Metadata-Version: 2.1
Name: django-behaviors
Version: 0.5.1
Summary: Common behaviors for Django Models, e.g. Timestamps, Publishing, Authoring/Editing and more.
Home-page: https://github.com/audiolion/django-behaviors
Author: Ryan Castner
Author-email: castner.rr@gmail.com
License: MIT
Description: =============================
        Django Behaviors
        =============================
        
        .. image:: https://badge.fury.io/py/django-behaviors.svg
            :target: https://badge.fury.io/py/django-behaviors
        
        .. image:: https://travis-ci.org/audiolion/django-behaviors.svg?branch=master
            :target: https://travis-ci.org/audiolion/django-behaviors
        
        .. image:: https://codecov.io/gh/audiolion/django-behaviors/branch/master/graph/badge.svg
            :target: https://codecov.io/gh/audiolion/django-behaviors
        
        
        Common behaviors for Django Models, e.g. Timestamps, Publishing, Authoring/Editing and more.
        
        Inspired by Kevin Stone's `Django Model Behaviors`_.
        
        Documentation
        =============
        
        Quickstart
        ----------
        
        Install Django Behaviors::
        
            pip install django-behaviors
            # Or, if you are going to use the Slugged behaviour
            pip install django-behaviors[slugged]
        
        Add it to your `INSTALLED_APPS`:
        
        .. code-block:: python
        
            INSTALLED_APPS = (
                ...
                'behaviors.apps.BehaviorsConfig',
                ...
            )
        
        Features
        --------
        
        ``behaviors`` makes it easy to integrate common behaviors into your django models:
        
        - **Documented**, **tested**, and **easy to use**
        - **Timestamped** to add ``created`` and ``modified`` attributes to your models
        - **StoreDeleted** to add ``deleted`` attribute to your models, avoiding the record to be deleted and allow to restore it
        - **Authored** to add an ``author`` to your models
        - **Editored** to add an ``editor`` to your models
        - **Published** to add a ``publication_status`` (draft or published) to your models
        - **Released** to add a ``release_date`` to your models
        - **Slugged** to add a ``slug`` to your models (thanks @apirobot) (ensure you have `awesome-slugify` installed, see above)
        - Easily compose together multiple ``behaviors`` to get desired functionality (e.g. ``Authored`` and ``Editored``)
        - Custom ``QuerySet`` methods added as managers to your models to utilize the added fields
        - Easily compose together multiple ``queryset`` or ``manager`` to get desired functionality
        
        Table of Contents
        -----------------
        
        - `Behaviors`_
           - `Timestamped`_
           - `StoreDeleted`_
           - `Authored`_
           - `Editored`_
           - `Published`_
           - `Released`_
           - `Slugged`_
        - `Mixing in with Custom Managers`_
        - `Mixing Multiple Behaviors`_
        
        Behaviors
        ---------
        
        Timestamped Behavior
        ``````````````````````
        
        The model adds a ``created`` and ``modified`` field to your model.
        
        .. code-block:: python
        
          class Timestamped(models.Model):
              """
              An abstract behavior representing timestamping a model with``created`` and
              ``modified`` fields.
              """
              created = models.DateTimeField(auto_now_add=True, db_index=True)
              modified = models.DateTimeField(null=True, blank=True, db_index=True)
        
              class Meta:
                  abstract = True
        
              @property
              def changed(self):
                  return True if self.modified else False
        
              def save(self, *args, **kwargs):
                  if self.pk:
                      self.modified = timezone.now()
                  return super(Timestamped, self).save(*args, **kwargs)
        
        ``created`` is set on the next save and is set to the current UTC time.
        ``modified`` is set when the object already exists and is set to the current UTC time.
        
        ``MyModel.changed`` returns a boolean representing if the object has been updated after created (the ``modified`` field has been set).
        
        Here is an example of using the model, note you do not need to add ``models.Model`` because ``Timestamped`` already inherits it.
        
        .. code-block:: python
        
            # models.py
            from behaviors.behaviors import Authored, Editored, Timestamped, Published
        
        
            class MyModel(Timestamped):
                name = models.CharField(max_length=100)
        
        
            >>> m = MyModel.objects.create(name='dj')
            >>> m.created
            '2017-02-14 17:20:19.835517+00:00'
            >>> m.modified
            None
            >>> m.changed
            False
            >>> m.save()
            >>> m.modified
            '2017-02-14 17:20:46.836395+00:00'
            >>> m.changed
            True
        
        StoreDeleted Behavior
        ``````````````````````
        
        The model add a ``deleted`` field to your model and prevent record to be deleted and allow to restore it
        
        .. code-block:: python
        
          class StoreDeleted(models.Model):
              """
              An abstract behavior representing store deleted a model with``deleted`` field,
              avoiding the model object to be deleted and allowing you to restore it.
              """
              deleted = models.DateTimeField(null=True, blank=True)
        
              objects = StoreDeletedQuerySet.as_manager()
        
              class Meta:
                  abstract = True
        
              @property
              def is_deleted(self):
                  return self.deleted != None
        
              def delete(self, *args, **kwargs):
                  if not self.pk:
                      raise ObjectDoesNotExist('Object must be created before it can be deleted')
                  self.deleted = timezone.now()
                  return super(StoreDeleted, self).save(*args, **kwargs)
        
              def restore(self, *args, **kwargs):
                  if not self.pk:
                      raise ObjectDoesNotExist('Object must be created before it can be restored')
                  self.deleted = None
                  return super(StoreDeleted, self).save(*args, **kwargs)
        
        ``deleted`` is set when ``delete()`` method is called, with current UTC time.
        
        Here is an example of using the model, note you do not need to add ``models.Model`` because ``StoreDeleted`` already inherits it.
        
        .. code-block:: python
        
            # models.py
            from behaviors.behaviors import StoreDeleted
        
        
            class GreatModel(StoreDeleted):
                name = models.CharField(max_length=100)
        
            # Deleting model
            >>> gm = GreatModel.objects.create(name='Xtra')
            >>> gm.deleted
            None
            >>> gm.delete()
            >>> gm.deleted
            '2018-05-14 08:35:41.197661+00:00'
        
            # Restoring model
            >>> gm = GreatModel.objects.deleted(name='Xtra')
            >>> gm.deleted
            '2018-05-14 08:35:41.197661+00:00'
            >>> gm.restore()
            >>> gm.deleted
            None
        
        
        Authored Behavior
        ``````````````````
        
        The authored model adds an ``author`` attribute that is a foreign key to the ``settings.AUTH_USER_MODEL`` and adds manager methods through ``objects`` and ``authors``.
        
        .. code-block:: python
        
          class Authored(models.Model):
              """
              An abstract behavior representing adding an author to a model based on the
              AUTH_USER_MODEL setting.
              """
              author = models.ForeignKey(
                  settings.AUTH_USER_MODEL,
                  related_name="%(app_label)s_%(class)s_author")
        
              objects = AuthoredQuerySet.as_manager()
              authors = AuthoredQuerySet.as_manager()
        
              class Meta:
                  abstract = True
        
        Here is an example of using the behavior and its ``authored_by()`` manager method:
        
        .. code-block:: python
        
            # models.py
            from behaviors.behaviors import Authored
        
        
            class MyModel(Authored):
                name = models.CharField(max_length=100)
        
            >>> m = MyModel.objects.create(author=User.objects.get(pk=2), name='tj')
            >>> m.author
            <User: ...>
            >>> queryset = MyModel.objects.authored_by(User.objects.get(pk=2))
            >>> queryset.count()
            1
        
        The author is a required field and must be provided on initial ``POST`` requests that create an object.
        
        A custom ``models.ModelForm`` is provided to automatically add the ``author``
        on object creation:
        
        .. code-block:: python
        
            # forms.py
            from behaviors.forms import AuthoredModelForm
            from .models import MyModel
        
        
            class MyModelForm(AuthoredModelForm):
                class Meta:
                  model = MyModel
                  fields = ['name']
        
            # views.py
            from django.views.generic.edit import CreateView
            from .forms import MyModelForm
            from .models import MyModel
        
        
            class MyModelCreateView(CreateView):
                model = MyModel
                form = MyModelForm
        
                # add request to form kwargs
                def get_form_kwargs(self):
                  kwargs = super(MyModelCreateView, self).get_form_kwargs()
                  kwargs['request'] = self.request
                  return kwargs
        
        Now when the object is created the ``author`` will be added on the call
        to ``form.save()``.
        
        If you are using functional views or another view type you simply need
        to make sure you pass the request object along with the form.
        
        .. code-block:: python
        
            # views.py
        
            class MyModelView(View):
              template_name = "myapp/mymodel_form.html"
        
              def get(self, request, *args, **kwargs):
                  context = {
                    'form': MyModelForm(),
                  }
                  return render(request, self.template_name, context=context)
        
              def post(self, request, *args, **kwargs):
                  # pass in request object to the request keyword argument
                  form = MyModelForm(self.request.POST, request=request)
                  if form.is_valid():
                      form.save()
                      return reverse(..)
                  context = {
                    'form': form,
                  }
                  return render(request, self.template_name, context=context)
        
        If for some reason you don't want to mixin the ``AuthoredModelForm`` with your existing
        form you can just add the user like so:
        
        .. code-block:: python
        
            # ...
            if form.is_valid()
                obj = form.save(commit=False)
                obj.author = request.user
                obj.save()
                return reverse(..)
            # ...
        
        But it isn't recommended, the ``AuthoredModelForm`` is tested and doesn't reassign the
        author on every save.
        
        The ``related_name`` is set so that it will never create conflicts. Given the above example if you wanted to do a reverse foreign key lookup from the User model and ``MyModel`` was part of the ``blogs`` app it could be done like so:
        
        .. code-block:: python
        
            >>> user = User.objects.get(pk=2)
            >>> user.blogs_mymodel_author.all()
            [<MyModel: ...>]
        
        That would give a list of all ``MyModel`` objects that ``user`` has ``authored``.
        
        Authored QuerySet
        ..................
        
        The ``Authored`` behavior attaches a custom model manager to the default ``objects``
        and to the ``authors`` variables on the model it is mixed into. If you haven't overrode
        the ``objects`` variable with a custom manager then you can use that, otherwise the
        ``authors`` variable is a fallback.
        
        To get all ``MyModel`` instances authored by people whose name starts with 'Jo'
        
        .. code-block:: python
        
            # case is insensitive so 'joe' or 'Joe' matches
            >>> MyModel.objects.authored_by('Jo')
            [<MyModel: ...>, <MyModel: ...>, ...]
        
            # or use the authors manager variable
            >>> MyModel.authors.authored_by('Jo')
            [<MyModel: ...>, <MyModel: ...>, ...]
        
        See `Mixing in with Custom Managers`_ for details on how
        to mix in this behavior with a custom manager you have that overrides the ``objects``
        default manager.
        
        
        Editored Behavior
        ``````````````````
        
        The editored model adds an ``editor`` attribute that is a foreign key to the ``settings.AUTH_USER_MODEL`` and adds manager methods through ``objects`` and ``editors`` variables.
        
        
        .. code-block:: python
        
            class Editored(models.Model):
            """
            An abstract behavior representing adding an editor to a model based on the
            AUTH_USER_MODEL setting.
            """
            editor = models.ForeignKey(
                settings.AUTH_USER_MODEL,
                related_name="%(app_label)s_%(class)s_editor",
                blank=True, null=True)
        
            objects = EditoredQuerySet.as_manager()
            editors = EditoredQuerySet.as_manager()
        
            class Meta:
                abstract = True
        
        The ``Editored`` model is similar to the ``Authored`` model except the foreign key is **not required**. Here is an example of its usage:
        
        .. code-block:: python
        
            # models.py
            from behaviors.behaviors import Editored
        
        
            class MyModel(Editored):
                name = models.CharField(max_length=100)
        
            >>> m = MyModel.objects.create(name='pj')
            >>> m.editor
            None
            >>> m.editor = User.objects.all()[0]
            >>> m.save()
            >>> queryset = MyModel.objects.edited_by(User.objects.all()[0])
            >>> queryset.count()
            1
        
        By default the ``editor`` is blank and null, if a ``request`` object is supplied to the form it will assign a new editor and erase the previous editor (or the null editor).
        
        Instead of using the ``AuthoredModelForm`` use the ``EditoredModelForm`` as a mixin to
        your form.
        
        .. code-block:: python
        
            # forms.py
            from behaviors.forms import EditoredModelForm
            from .models import MyModel
        
        
            class MyModelForm(EditoredModelForm):
                class Meta:
                  model = MyModel
                  fields = ['name']
        
            # views.py
            from django.views.generic.edit import CreateView, UpdateView
            from .forms import MyModelForm
            from .models import MyModel
        
        
            MyModelRequestFormMixin(object):
                # add request to form kwargs
                def get_form_kwargs(self):
                  kwargs = super(MyModelCreateView, self).get_form_kwargs()
                  kwargs['request'] = self.request
                  return kwargs
        
        
            class MyModelCreateView(MyModelRequestFormMixin, CreateView):
                model = MyModel
                form = MyModelForm
        
        
            class MyModelUpdateView(MyModelRequestFormMixin, UpdateView):
                model = MyModel
                form = MyModelForm
        
        
        Now when the object is created or updated the ``editor`` will be updated
        on the call to ``form.save()``.
        
        If you are using functional views or another view type you simply need
        to make sure you pass the request object along with the form.
        
        .. code-block:: python
        
            # views.py
        
            class MyModelView(View):
              template_name = "myapp/mymodel_form.html"
        
              def get(self, request, *args, **kwargs):
                  context = {
                    'form': MyModelForm(),
                  }
                  return render(request, self.template_name, context=context)
        
              def post(self, request, *args, **kwargs):
                  # pass in request object to the request keyword argument
                  form = MyModelForm(self.request.POST, request=request)
                  if form.is_valid():
                      form.save()
                      return reverse(..)
                  context = {
                    'form': form,
                  }
                  return render(request, self.template_name, context=context)
        
        If for some reason you don't want to mixin the ``EditoredModelForm`` with your existing
        form you can just add the user like so:
        
        .. code-block:: python
        
            ...
            if form.is_valid()
                obj = form.save(commit=False)
                obj.editor = request.user
                obj.save()
                return reverse(..)
            ...
        
        But it isn't recommended, the ``EditoredModelForm`` is tested and doesn't cause errors
        if request.user is invalid.
        
        The ``related_name`` is set so that it will never create conflicts. Given the above example if you wanted to do a reverse foreign key lookup from the User model and ``MyModel`` was part of the ``blogs`` app it could be done like so:
        
        .. code-block:: python
        
            >>> user = User.objects.get(pk=2)
            >>> user.blogs_mymodel_editor.all()
            [<MyModel: ...>]
        
        That would give a list of all ``MyModel`` objects that ``user`` is an ``editor``.
        
        Editored QuerySet
        ..................
        
        The ``Editored`` behavior attaches a custom model manager to the default ``objects``
        and to the ``editors`` variables on the model it is mixed into. If you haven't overrode
        the ``objects`` variable with a custom manager then you can use that, otherwise the
        ``editors`` variable is a fallback.
        
        To get all ``MyModel`` instances edited by people whose name starts with 'Jo'
        
        .. code-block:: python
        
            # case is insensitive so 'joe' or 'Joe' matches
            >>> MyModel.objects.edited_by('Jo')
            [<MyModel: ...>, <MyModel: ...>, ...]
        
            # or use the editors manager variable
            >>> MyModel.editors.edited_by('Jo')
            [<MyModel: ...>, <MyModel: ...>, ...]
        
        See `Mixing in with Custom Managers`_ for details on how
        to mix in this behavior with a custom manager you have that overrides the ``objects``
        default manager.
        
        Published Behavior
        ````````````````````
        
        The ``Published`` behavior adds a field ``publication_status`` to your model. The status
        has two states: 'Draft' or 'Published'.
        
        .. code-block:: python
        
            class Published(models.Model):
                """
                An abstract behavior representing adding a publication status. A
                ``publication_status`` is set on the model with Draft or Published
                options.
                """
                DRAFT = 'd'
                PUBLISHED = 'p'
        
                PUBLICATION_STATUS_CHOICES = (
                    (DRAFT, 'Draft'),
                    (PUBLISHED, 'Published'),
                )
        
                publication_status = models.CharField(
                    "Publication Status", max_length=1,
                    choices=PUBLICATION_STATUS_CHOICES, default=DRAFT)
        
                class Meta:
                    abstract = True
        
                objects = PublishedQuerySet.as_manager()
                publications = PublishedQuerySet.as_manager()
        
                @property
                def draft(self):
                    return self.publication_status == self.DRAFT
        
                @property
                def published(self):
                    return self.publication_status == self.PUBLISHED
        
        The class offers two properties ``draft`` and ``published`` to know object state. The ``DRAFT`` and ``PUBLISHED`` class constants will be available from the class the ``Published`` behavior is mixed into. There is also a custom manager attached to ``objects`` and ``publications`` variables to get ``published()`` or ``draft()`` querysets.
        
        .. code-block:: python
        
            # models.py
            from behaviors.behaviors import Published
        
        
            class MyModel(Published):
                name = models.CharField(max_length=100)
        
            >>> m = MyModel.objects.create(name='cj')
            >>> m.publication_status
            u'd'
            >>> m.draft
            True
            >>> m.published
            False
            >>> m.get_publication_status_display()
            u'Draft'
            >>> MyModel.objects.published().count()
            0
            >>> MyModel.objects.draft().count()
            1
            >>> m.publication_status = MyModel.PUBLISHED
            >>> m.save()
            >>> m.publication_status
            u'p'
            >>> m.draft
            False
            >>> m.published
            True
            >>> m.get_publication_status_display()
            u'Published'
            >>> MyModel.objects.published().count()
            1
            >>> MyModel.PUBLISHED
            u'p'
            >>> MyModel.PUBLISHED == m.publication_status
            True
        
        The ``publication_status`` field defaults to ``Published.DRAFT`` when you make new
        models unless you supply the ``Published.PUBLISHED`` attribute to the ``publication_status``
        field.
        
        .. code-block:: python
        
            MyModel.objects.create(name='Jim-bob Cooter', publication_status=MyModel.PUBLISHED)
        
        Published QuerySet
        ...................
        
        The ``Published`` behavior attaches to the default ``objects`` variable and
        the ``publications`` variable as a fallback if ``objects`` is overrode.
        
        .. code-block:: python
        
            # returns all MyModel.PUBLISHED
            MyModel.objects.published()
            MyModel.publications.published()
        
            # returns all MyModel.DRAFT
            MyModel.objects.draft()
            MyModel.publications.draft()
        
        
        Released Behavior
        ``````````````````
        
        The ``Released`` behavior adds a field ``release_date`` to your model. The field
        is **not_required**. The release date can be set with the ``release_on(datetime)`` method.
        
        .. code-block:: python
        
            class Released(models.Model):
                """
                An abstract behavior representing a release_date for a model to
                indicate when it should be listed publically.
                """
                release_date = models.DateTimeField(null=True, blank=True)
        
                class Meta:
                    abstract = True
        
                objects = ReleasedQuerySet.as_manager()
                releases = ReleasedQuerySet.as_manager()
        
                def release_on(self, date=None):
                    if not date:
                        date = timezone.now()
                    self.release_date = date
                    self.save()
        
                @property
                def released(self):
                    return self.release_date and self.release_date < timezone.now()
        
        There is a ``released`` property added which determines if the object has been released. There is a custom manager attached to ``objects`` and ``releases`` variables to filter querysets on their release date.
        
        Here is an example of using the behavior:
        
        .. code-block:: python
        
            # models.py
            from django.utils import timezone
            from datetime import timedelta
            from behaviors.behaviors import Released
        
        
            class MyModel(Released):
                name = models.CharField(max_length=100)
        
            >>> m = MyModel.objects.create(name='rj')
            >>> m.release_date
            None
            >>> MyModel.objects.no_release_date().count()
            1
            >>> m.release_on()
            >>> MyModel.objects.no_release_date().count()
            0
            >>> MyModel.objects.released().count()
            1
            >>> m.release_on(timezone.now() + timedelta(weeks=1))
            >>> MyModel.objects.not_released().count()
            1
            >>> MyModel.objects.released().count()
            0
        
        The ``release_on`` method defaults to the current time so that the object is immediately
        released. You can also provide a date to the method to release on a certain date. ``release_on()`` just serves as a wrapper to setting and saving the date.
        
        You can always provide a ``release_date`` on object creation:
        
        .. code-block:: python
        
            MyModel.objects.create(name='Jim-bob Cooter', release_date=timezone.now())
        
        
        Released QuerySet
        ...................
        
        The ``Released`` behavior attaches to the default ``objects`` variable and
        the ``releases`` variable as a fallback if ``objects`` is overrode.
        
        .. code-block:: python
        
            # returns all not released MyModel objects
            MyModel.objects.not_released()
            MyModel.releases.not_released()
        
            # returns all released MyModel objects
            MyModel.objects.released()
            MyModel.releases.released()
        
            # returns all null release date MyModel objects
            MyModel.objects.no_release_date()
            MyModel.releases.no_release_date()
        
        Slugged Behavior
        ``````````````````
        
        The ``Slugged`` behavior allows you to easily add a ``slug`` field to your model. The slug is generated on the first model creation or the next model save and is based on the ``slug_source`` attribute.
        
        **The** ``slug_source`` **property has no set default, you must add it to your model for the behavior to work.**
        
        .. code-block:: python
        
            class Slugged(models.Model):
                """
                An abstract behavior representing adding a slug (by default, unique) to
                a model based on the slug_source property.
                """
                slug = models.SlugField(
                    max_length=255,
                    unique=BehaviorsConfig.are_slug_unique(),
                    blank=True)
        
                class Meta:
                    abstract = True
        
                def save(self, *args, **kwargs):
                    if not self.slug:
                        self.slug = self.generate_unique_slug() \
                            if BehaviorsConfig.are_slug_unique() else self.get_slug()
                    super(Slugged, self).save(*args, **kwargs)
        
                def get_slug(self):
                    return slugify(getattr(self, "slug_source"), to_lower=True)
        
                def is_unique_slug(self, slug):
                    qs = self.__class__.objects.filter(slug=slug)
                    return not qs.exists()
        
                def generate_unique_slug(self):
                    slug = self.get_slug()
                    new_slug = slug
        
                    iteration = 1
                    while not self.is_unique_slug(new_slug):
                        new_slug = "%s-%d" % (slug, iteration)
                        iteration += 1
        
                    return new_slug
        
        The ``slug`` uses the awesome-slugify package which will preserve unicode
        character slugs. By default, the ``slug`` must be unique and is guaranteed to
        be unique by the class appending a number ``-[0-9+]`` to the end of the slug
        if it is not unique. The ``unique`` field type `adds an index`_ to the ``slug`` field.
        
        Add the ``slug_source`` property to your class when mixing in the behavior.
        
        To allow non-unique slugs, add ``UNIQUE_SLUG_BEHAVIOR = False`` to your project's settings.
        
        .. code-block:: python
        
            # models.py
            from behaviors.behaviors import Slugged
        
        
            class MyModel(Slugged):
                name = models.CharField(max_length=100)
        
                # slug_source is required for the slug to be set
                @property
                def slug_source(self):
                  return "prepended-text-for-fun-{}".format(self.name)
        
                # you can now use the slug for your get_absolute_url() method
                def get_absolute_url(self):
                  return reverse('myapp:mymodel_detail', args=[self.slug])
        
            >>> m = MyModel.objects.create(name='aj')
            >>> m.slug
            'prepended-text-for-fun-aj'
            >>> m2 = MyModel.objects.create(name='aj')
            >>> m.slug
            'prepended-text-for-fun-aj-1'
            >>> m.get_absolute_url()
            '/myapp/prepended-text-for-fun-aj/detail'
        
        Your ``slug_source`` attribute can be a mix of any of the model data available at the time of save, generally it is some ``name`` type of field. You could also hash the primary key and/or some other data as a ``slug_source``.
        By default, the ``slug`` is unique so it can be used to define the ``get_absolute_url()`` method on your model.
        
        Thanks to @apirobot for sending the PR for the ``Slugged`` behavior.
        
        Mixing in with Custom Managers
        ------------------------------
        
        If you have a custom manager on your model already:
        
        .. code-block:: python
        
            # models.py
            from behaviors.behaviors import Authored, Editored, Published, Timestamped
        
            from django.db import models
        
        
            class MyModelCustomManager(models.Manager):
        
                def get_queryset(self):
                    return super(MyModelCustomManager).get_queryset(self)
        
                def custom_manager_method(self):
                    return self.get_queryset().filter(name='Jim-bob')
        
            class MyModel(Authored):
                name = models.CharField(max_length=100)
        
                # MyModel.objects.authored_by(..) won't work
                # MyModel.authors.authored_by(..) still will
                objects = MyModelCustomManager()
        
        Simply add ``AuthoredManager`` from ``behaviors.managers`` as a mixin to
        ``MyModelCustomManager`` so they can share the ``objects`` variable.
        
        .. code-block:: python
        
            # models.py
            from behaviors.behaviors import Authored, Editored, Published, Timestamped
            from behaviors.managers import AuthoredManager, EditoredManager, PublishedManager
        
            from django.db import models
        
        
            class MyModelCustomManager(AuthoredManager, models.Manager):
        
                def get_queryset(self):
                    return super(MyModelCustomManager).get_queryset(self)
        
                def custom_manager_method(self):
                    return self.get_queryset().filter(name='Jim-bob')
        
            class MyModel(Authored):
                name = models.CharField(max_length=100)
        
                # MyModel.objects.authored_by(..) now works
                objects = MyModelCustomManager()
        
        Similarly if you are using a custom QuerySet and calling its ``as_manager()``
        method to attach it to ``objects`` you can import from ``behaviors.querysets``
        and mix it in.
        
        .. code-block:: python
        
            # models.py
            from behaviors.behaviors import Authored, Editored, Published, Timestamped
            from behaviors.querysets import AuthoredQuerySet, EditoredQuerySet, PublishedQuerySet
        
            from django.db import models
        
        
            class MyModelCustomQuerySet(AuthoredQuerySet, models.QuerySet):
        
                def custom_queryset_method(self):
                    return self.filter(name='Jim-bob')
        
            class MyModel(Authored):
                name = models.CharField(max_length=100)
        
                # MyModel.objects.authored_by(..) works
                objects = MyModelCustomQuerySet.as_manager()
        
        
        Mixing in Multiple Behaviors
        ----------------------------
        
        Many times you will want multiple behaviors on a model. You can simply mix in
        multiple behaviors and, if you'd like to have all their custom ``QuerySet``
        methods work on ``objects``, provide a custom manager with all the mixins.
        
        .. code-block:: python
        
            # models.py
            from behaviors.behaviors import Authored, Editored, Published, Timestamped
            from behaviors.querysets import AuthoredQuerySet, EditoredQuerySet, PublishedQuerySet
        
            from django.db import models
        
        
            class MyModelQuerySet(AuthoredQuerySet, EditoredQuerySet, PublishedQuerySet):
                pass
        
            class MyModel(Authored, Editored, Published, Timestamped):
                name = models.CharField(max_length=100)
        
                # MyModel.objects.authored_by(..) works
                # MyModel.objects.edited_by(..) works
                # MyModel.objects.published() works
                # MyModel.objects.draft() works
                objects = MyModelQuerySet.as_manager()
        
            # you can also chain queryset methods
            >>> u = User.objects.all()[0]
            >>> u2 = User.objects.all()[1]
            >>> m = MyModel.objects.create(author=u, editor=u2)
            >>> MyModel.objects.published().authored_by(u).count()
            1
        
        
        Running Tests
        -------------
        
        Does the code actually work?
        
        ::
        
            source <YOURVIRTUALENV>/bin/activate
            (myenv) $ pip install tox
            (myenv) $ tox
        
        Credits
        -------
        
        Tools used in rendering this package:
        
        *  Cookiecutter_
        *  `cookiecutter-djangopackage`_
        
        .. _Cookiecutter: https://github.com/audreyr/cookiecutter
        .. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage
        
        .. _`Timestamped`: #timestamped-behavior
        .. _`StoreDeleted`: #storedeleted-behavior
        .. _`Authored`: #authored-behavior
        .. _`Editored`: #editored-behavior
        .. _`Published`: #published-behavior
        .. _`Released`: #released-behavior
        .. _`Slugged`: #slugged-behavior
        .. _`settings.AUTH_USER_MODEL`: https://docs.djangoproject.com/en/1.10/ref/settings/#std:setting-AUTH_USER_MODEL
        .. _`Mixing in with Custom Managers`: #mixing-in-with-custom-managers
        .. _`Mixing Multiple Behaviors`: #mixing-in-multiple-behaviors
        .. _`Django Model Behaviors`: http://blog.kevinastone.com/django-model-behaviors.html
        .. _`adds an index`: https://docs.djangoproject.com/en/dev/ref/models/fields/#unique
        
        
        
        
        History
        =======
        
        0.5.1 (2020-09-19)
        ------------------
        
        * Feature: ``Slugged`` behavior now supports non-unique slugs (thanks @vonmaster)
        
        0.5.0 (2019-07-01)
        ------------------
        
        * Drop Django 2.0 support and Python 3.4 support as they have reached EOL
        * Add Django 2.1, 2.2 and Python 3.7 support
        * Feature: Add db_index to ``Timestamped`` behavior's ``modified`` field (thanks @kazqvaizer)
        
        0.4.1 (2018-05-17)
        ------------------
        
        * Feature: Add ``StoreDeleted`` behavior to soft delete models (thanks @abekroenem)
        
        0.4.0 (2018-01-16)
        ------------------
        
        * Update setup.py classifiers
        
        0.3.1 (2018-01-04)
        ------------------
        
        * Add Django 2.0 and Python 3.6 Support
        
        0.3.0 (2017-03-11)
        ------------------
        
        * Add ``Slugged`` behavior adding a slug to models
        * Update documentation
        
        0.2.0 (2017-02-14)
        ------------------
        
        * Add ``Released`` behavior for a release date on models
        * Update documentation
        
        0.1.7 (2017-02-14)
        ------------------
        
        * Remove an unused import
        * Integrate with Lintly
        
        0.1.6 (2017-02-14)
        ------------------
        
        * Drop python3.3 support for Django 1.8 because 1.8 no longer supports it
        
        0.1.5 (2017-02-14)
        ------------------
        
        * Fix import error for py2.7 builds
        
        0.1.4 (2017-02-14)
        ------------------
        
        * Fix Syntax Error
        
        0.1.3 (2017-02-14)
        ------------------
        
        * Fixed Circular Import
        
        0.1.2 (2017-02-13)
        ------------------
        
        * Travis CI Fixes
        
        0.1.1 (2017-02-13)
        ------------------
        
        * First release on PyPI
        * Flake8 adherence fixes
        
        0.1.0 (2017-02-13)
        ------------------
        
        * First push of project
        
Keywords: django-behaviors
Platform: UNKNOWN
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Django
Classifier: Framework :: Django :: 1.8
Classifier: Framework :: Django :: 1.11
Classifier: Framework :: Django :: 2.0
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Natural Language :: English
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
Description-Content-Type: text/x-rst
