{"id":13813704,"url":"https://github.com/audiolion/django-behaviors","last_synced_at":"2025-04-05T07:04:38.659Z","repository":{"id":57419080,"uuid":"81869805","full_name":"audiolion/django-behaviors","owner":"audiolion","description":"Easily integrate common behaviors for Django models, e.g. Timestamps, Publishing, Authoring, Editing and more.","archived":false,"fork":false,"pushed_at":"2022-12-26T20:29:36.000Z","size":89,"stargazers_count":216,"open_issues_count":3,"forks_count":10,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-03-29T06:07:19.616Z","etag":null,"topics":["behaviors","django","hacktoberfest","models"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/audiolion.png","metadata":{"files":{"readme":"README.rst","changelog":"HISTORY.rst","contributing":"CONTRIBUTING.rst","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-02-13T20:44:35.000Z","updated_at":"2025-02-17T23:38:30.000Z","dependencies_parsed_at":"2023-01-31T01:15:46.816Z","dependency_job_id":null,"html_url":"https://github.com/audiolion/django-behaviors","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/audiolion%2Fdjango-behaviors","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/audiolion%2Fdjango-behaviors/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/audiolion%2Fdjango-behaviors/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/audiolion%2Fdjango-behaviors/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/audiolion","download_url":"https://codeload.github.com/audiolion/django-behaviors/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247299831,"owners_count":20916190,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["behaviors","django","hacktoberfest","models"],"created_at":"2024-08-04T04:01:26.424Z","updated_at":"2025-04-05T07:04:38.633Z","avatar_url":"https://github.com/audiolion.png","language":"Python","funding_links":[],"categories":["Python"],"sub_categories":[],"readme":"=============================\nDjango Behaviors\n=============================\n\n.. image:: https://badge.fury.io/py/django-behaviors.svg\n    :target: https://badge.fury.io/py/django-behaviors\n\n.. image:: https://travis-ci.org/audiolion/django-behaviors.svg?branch=master\n    :target: https://travis-ci.org/audiolion/django-behaviors\n\n.. image:: https://codecov.io/gh/audiolion/django-behaviors/branch/master/graph/badge.svg\n    :target: https://codecov.io/gh/audiolion/django-behaviors\n\n\nCommon behaviors for Django Models, e.g. Timestamps, Publishing, Authoring/Editing and more.\n\nInspired by Kevin Stone's `Django Model Behaviors`_.\n\nDocumentation\n=============\n\nQuickstart\n----------\n\nInstall Django Behaviors::\n\n    pip install django-behaviors\n    # Or, if you are going to use the Slugged behaviour\n    pip install django-behaviors[slugged]\n\nAdd it to your `INSTALLED_APPS`:\n\n.. code-block:: python\n\n    INSTALLED_APPS = (\n        ...\n        'behaviors.apps.BehaviorsConfig',\n        ...\n    )\n\nFeatures\n--------\n\n``behaviors`` makes it easy to integrate common behaviors into your django models:\n\n- **Documented**, **tested**, and **easy to use**\n- **Timestamped** to add ``created`` and ``modified`` attributes to your models\n- **StoreDeleted** to add ``deleted`` attribute to your models, avoiding the record to be deleted and allow to restore it\n- **Authored** to add an ``author`` to your models\n- **Editored** to add an ``editor`` to your models\n- **Published** to add a ``publication_status`` (draft or published) to your models\n- **Released** to add a ``release_date`` to your models\n- **Slugged** to add a ``slug`` to your models (thanks @apirobot) (ensure you have `awesome-slugify` installed, see above)\n- Easily compose together multiple ``behaviors`` to get desired functionality (e.g. ``Authored`` and ``Editored``)\n- Custom ``QuerySet`` methods added as managers to your models to utilize the added fields\n- Easily compose together multiple ``queryset`` or ``manager`` to get desired functionality\n\nTable of Contents\n-----------------\n\n- `Behaviors`_\n   - `Timestamped`_\n   - `StoreDeleted`_\n   - `Authored`_\n   - `Editored`_\n   - `Published`_\n   - `Released`_\n   - `Slugged`_\n- `Mixing in with Custom Managers`_\n- `Mixing Multiple Behaviors`_\n\nBehaviors\n---------\n\nTimestamped Behavior\n``````````````````````\n\nThe model adds a ``created`` and ``modified`` field to your model.\n\n.. code-block:: python\n\n  class Timestamped(models.Model):\n      \"\"\"\n      An abstract behavior representing timestamping a model with``created`` and\n      ``modified`` fields.\n      \"\"\"\n      created = models.DateTimeField(auto_now_add=True, db_index=True)\n      modified = models.DateTimeField(null=True, blank=True, db_index=True)\n\n      class Meta:\n          abstract = True\n\n      @property\n      def changed(self):\n          return True if self.modified else False\n\n      def save(self, *args, **kwargs):\n          if self.pk:\n              self.modified = timezone.now()\n          return super(Timestamped, self).save(*args, **kwargs)\n\n``created`` is set on the next save and is set to the current UTC time.\n``modified`` is set when the object already exists and is set to the current UTC time.\n\n``MyModel.changed`` returns a boolean representing if the object has been updated after created (the ``modified`` field has been set).\n\nHere is an example of using the model, note you do not need to add ``models.Model`` because ``Timestamped`` already inherits it.\n\n.. code-block:: python\n\n    # models.py\n    from behaviors.behaviors import Authored, Editored, Timestamped, Published\n\n\n    class MyModel(Timestamped):\n        name = models.CharField(max_length=100)\n\n\n    \u003e\u003e\u003e m = MyModel.objects.create(name='dj')\n    \u003e\u003e\u003e m.created\n    '2017-02-14 17:20:19.835517+00:00'\n    \u003e\u003e\u003e m.modified\n    None\n    \u003e\u003e\u003e m.changed\n    False\n    \u003e\u003e\u003e m.save()\n    \u003e\u003e\u003e m.modified\n    '2017-02-14 17:20:46.836395+00:00'\n    \u003e\u003e\u003e m.changed\n    True\n\nStoreDeleted Behavior\n``````````````````````\n\nThe model add a ``deleted`` field to your model and prevent record to be deleted and allow to restore it\n\n.. code-block:: python\n\n  class StoreDeleted(models.Model):\n      \"\"\"\n      An abstract behavior representing store deleted a model with``deleted`` field,\n      avoiding the model object to be deleted and allowing you to restore it.\n      \"\"\"\n      deleted = models.DateTimeField(null=True, blank=True)\n\n      objects = StoreDeletedQuerySet.as_manager()\n\n      class Meta:\n          abstract = True\n\n      @property\n      def is_deleted(self):\n          return self.deleted != None\n\n      def delete(self, *args, **kwargs):\n          if not self.pk:\n              raise ObjectDoesNotExist('Object must be created before it can be deleted')\n          self.deleted = timezone.now()\n          return super(StoreDeleted, self).save(*args, **kwargs)\n\n      def restore(self, *args, **kwargs):\n          if not self.pk:\n              raise ObjectDoesNotExist('Object must be created before it can be restored')\n          self.deleted = None\n          return super(StoreDeleted, self).save(*args, **kwargs)\n\n``deleted`` is set when ``delete()`` method is called, with current UTC time.\n\nHere is an example of using the model, note you do not need to add ``models.Model`` because ``StoreDeleted`` already inherits it.\n\n.. code-block:: python\n\n    # models.py\n    from behaviors.behaviors import StoreDeleted\n\n\n    class GreatModel(StoreDeleted):\n        name = models.CharField(max_length=100)\n\n    # Deleting model\n    \u003e\u003e\u003e gm = GreatModel.objects.create(name='Xtra')\n    \u003e\u003e\u003e gm.deleted\n    None\n    \u003e\u003e\u003e gm.delete()\n    \u003e\u003e\u003e gm.deleted\n    '2018-05-14 08:35:41.197661+00:00'\n\n    # Restoring model\n    \u003e\u003e\u003e gm = GreatModel.objects.deleted(name='Xtra')\n    \u003e\u003e\u003e gm.deleted\n    '2018-05-14 08:35:41.197661+00:00'\n    \u003e\u003e\u003e gm.restore()\n    \u003e\u003e\u003e gm.deleted\n    None\n\n\nAuthored Behavior\n``````````````````\n\nThe 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``.\n\n.. code-block:: python\n\n  class Authored(models.Model):\n      \"\"\"\n      An abstract behavior representing adding an author to a model based on the\n      AUTH_USER_MODEL setting.\n      \"\"\"\n      author = models.ForeignKey(\n          settings.AUTH_USER_MODEL,\n          related_name=\"%(app_label)s_%(class)s_author\")\n\n      objects = AuthoredQuerySet.as_manager()\n      authors = AuthoredQuerySet.as_manager()\n\n      class Meta:\n          abstract = True\n\nHere is an example of using the behavior and its ``authored_by()`` manager method:\n\n.. code-block:: python\n\n    # models.py\n    from behaviors.behaviors import Authored\n\n\n    class MyModel(Authored):\n        name = models.CharField(max_length=100)\n\n    \u003e\u003e\u003e m = MyModel.objects.create(author=User.objects.get(pk=2), name='tj')\n    \u003e\u003e\u003e m.author\n    \u003cUser: ...\u003e\n    \u003e\u003e\u003e queryset = MyModel.objects.authored_by(User.objects.get(pk=2))\n    \u003e\u003e\u003e queryset.count()\n    1\n\nThe author is a required field and must be provided on initial ``POST`` requests that create an object.\n\nA custom ``models.ModelForm`` is provided to automatically add the ``author``\non object creation:\n\n.. code-block:: python\n\n    # forms.py\n    from behaviors.forms import AuthoredModelForm\n    from .models import MyModel\n\n\n    class MyModelForm(AuthoredModelForm):\n        class Meta:\n          model = MyModel\n          fields = ['name']\n\n    # views.py\n    from django.views.generic.edit import CreateView\n    from .forms import MyModelForm\n    from .models import MyModel\n\n\n    class MyModelCreateView(CreateView):\n        model = MyModel\n        form = MyModelForm\n\n        # add request to form kwargs\n        def get_form_kwargs(self):\n          kwargs = super(MyModelCreateView, self).get_form_kwargs()\n          kwargs['request'] = self.request\n          return kwargs\n\nNow when the object is created the ``author`` will be added on the call\nto ``form.save()``.\n\nIf you are using functional views or another view type you simply need\nto make sure you pass the request object along with the form.\n\n.. code-block:: python\n\n    # views.py\n\n    class MyModelView(View):\n      template_name = \"myapp/mymodel_form.html\"\n\n      def get(self, request, *args, **kwargs):\n          context = {\n            'form': MyModelForm(),\n          }\n          return render(request, self.template_name, context=context)\n\n      def post(self, request, *args, **kwargs):\n          # pass in request object to the request keyword argument\n          form = MyModelForm(self.request.POST, request=request)\n          if form.is_valid():\n              form.save()\n              return reverse(..)\n          context = {\n            'form': form,\n          }\n          return render(request, self.template_name, context=context)\n\nIf for some reason you don't want to mixin the ``AuthoredModelForm`` with your existing\nform you can just add the user like so:\n\n.. code-block:: python\n\n    # ...\n    if form.is_valid()\n        obj = form.save(commit=False)\n        obj.author = request.user\n        obj.save()\n        return reverse(..)\n    # ...\n\nBut it isn't recommended, the ``AuthoredModelForm`` is tested and doesn't reassign the\nauthor on every save.\n\nThe ``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:\n\n.. code-block:: python\n\n    \u003e\u003e\u003e user = User.objects.get(pk=2)\n    \u003e\u003e\u003e user.blogs_mymodel_author.all()\n    [\u003cMyModel: ...\u003e]\n\nThat would give a list of all ``MyModel`` objects that ``user`` has ``authored``.\n\nAuthored QuerySet\n..................\n\nThe ``Authored`` behavior attaches a custom model manager to the default ``objects``\nand to the ``authors`` variables on the model it is mixed into. If you haven't overrode\nthe ``objects`` variable with a custom manager then you can use that, otherwise the\n``authors`` variable is a fallback.\n\nTo get all ``MyModel`` instances authored by people whose name starts with 'Jo'\n\n.. code-block:: python\n\n    # case is insensitive so 'joe' or 'Joe' matches\n    \u003e\u003e\u003e MyModel.objects.authored_by('Jo')\n    [\u003cMyModel: ...\u003e, \u003cMyModel: ...\u003e, ...]\n\n    # or use the authors manager variable\n    \u003e\u003e\u003e MyModel.authors.authored_by('Jo')\n    [\u003cMyModel: ...\u003e, \u003cMyModel: ...\u003e, ...]\n\nSee `Mixing in with Custom Managers`_ for details on how\nto mix in this behavior with a custom manager you have that overrides the ``objects``\ndefault manager.\n\n\nEditored Behavior\n``````````````````\n\nThe 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.\n\n\n.. code-block:: python\n\n    class Editored(models.Model):\n    \"\"\"\n    An abstract behavior representing adding an editor to a model based on the\n    AUTH_USER_MODEL setting.\n    \"\"\"\n    editor = models.ForeignKey(\n        settings.AUTH_USER_MODEL,\n        related_name=\"%(app_label)s_%(class)s_editor\",\n        blank=True, null=True)\n\n    objects = EditoredQuerySet.as_manager()\n    editors = EditoredQuerySet.as_manager()\n\n    class Meta:\n        abstract = True\n\nThe ``Editored`` model is similar to the ``Authored`` model except the foreign key is **not required**. Here is an example of its usage:\n\n.. code-block:: python\n\n    # models.py\n    from behaviors.behaviors import Editored\n\n\n    class MyModel(Editored):\n        name = models.CharField(max_length=100)\n\n    \u003e\u003e\u003e m = MyModel.objects.create(name='pj')\n    \u003e\u003e\u003e m.editor\n    None\n    \u003e\u003e\u003e m.editor = User.objects.all()[0]\n    \u003e\u003e\u003e m.save()\n    \u003e\u003e\u003e queryset = MyModel.objects.edited_by(User.objects.all()[0])\n    \u003e\u003e\u003e queryset.count()\n    1\n\nBy 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).\n\nInstead of using the ``AuthoredModelForm`` use the ``EditoredModelForm`` as a mixin to\nyour form.\n\n.. code-block:: python\n\n    # forms.py\n    from behaviors.forms import EditoredModelForm\n    from .models import MyModel\n\n\n    class MyModelForm(EditoredModelForm):\n        class Meta:\n          model = MyModel\n          fields = ['name']\n\n    # views.py\n    from django.views.generic.edit import CreateView, UpdateView\n    from .forms import MyModelForm\n    from .models import MyModel\n\n\n    MyModelRequestFormMixin(object):\n        # add request to form kwargs\n        def get_form_kwargs(self):\n          kwargs = super(MyModelCreateView, self).get_form_kwargs()\n          kwargs['request'] = self.request\n          return kwargs\n\n\n    class MyModelCreateView(MyModelRequestFormMixin, CreateView):\n        model = MyModel\n        form = MyModelForm\n\n\n    class MyModelUpdateView(MyModelRequestFormMixin, UpdateView):\n        model = MyModel\n        form = MyModelForm\n\n\nNow when the object is created or updated the ``editor`` will be updated\non the call to ``form.save()``.\n\nIf you are using functional views or another view type you simply need\nto make sure you pass the request object along with the form.\n\n.. code-block:: python\n\n    # views.py\n\n    class MyModelView(View):\n      template_name = \"myapp/mymodel_form.html\"\n\n      def get(self, request, *args, **kwargs):\n          context = {\n            'form': MyModelForm(),\n          }\n          return render(request, self.template_name, context=context)\n\n      def post(self, request, *args, **kwargs):\n          # pass in request object to the request keyword argument\n          form = MyModelForm(self.request.POST, request=request)\n          if form.is_valid():\n              form.save()\n              return reverse(..)\n          context = {\n            'form': form,\n          }\n          return render(request, self.template_name, context=context)\n\nIf for some reason you don't want to mixin the ``EditoredModelForm`` with your existing\nform you can just add the user like so:\n\n.. code-block:: python\n\n    ...\n    if form.is_valid()\n        obj = form.save(commit=False)\n        obj.editor = request.user\n        obj.save()\n        return reverse(..)\n    ...\n\nBut it isn't recommended, the ``EditoredModelForm`` is tested and doesn't cause errors\nif request.user is invalid.\n\nThe ``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:\n\n.. code-block:: python\n\n    \u003e\u003e\u003e user = User.objects.get(pk=2)\n    \u003e\u003e\u003e user.blogs_mymodel_editor.all()\n    [\u003cMyModel: ...\u003e]\n\nThat would give a list of all ``MyModel`` objects that ``user`` is an ``editor``.\n\nEditored QuerySet\n..................\n\nThe ``Editored`` behavior attaches a custom model manager to the default ``objects``\nand to the ``editors`` variables on the model it is mixed into. If you haven't overrode\nthe ``objects`` variable with a custom manager then you can use that, otherwise the\n``editors`` variable is a fallback.\n\nTo get all ``MyModel`` instances edited by people whose name starts with 'Jo'\n\n.. code-block:: python\n\n    # case is insensitive so 'joe' or 'Joe' matches\n    \u003e\u003e\u003e MyModel.objects.edited_by('Jo')\n    [\u003cMyModel: ...\u003e, \u003cMyModel: ...\u003e, ...]\n\n    # or use the editors manager variable\n    \u003e\u003e\u003e MyModel.editors.edited_by('Jo')\n    [\u003cMyModel: ...\u003e, \u003cMyModel: ...\u003e, ...]\n\nSee `Mixing in with Custom Managers`_ for details on how\nto mix in this behavior with a custom manager you have that overrides the ``objects``\ndefault manager.\n\nPublished Behavior\n````````````````````\n\nThe ``Published`` behavior adds a field ``publication_status`` to your model. The status\nhas two states: 'Draft' or 'Published'.\n\n.. code-block:: python\n\n    class Published(models.Model):\n        \"\"\"\n        An abstract behavior representing adding a publication status. A\n        ``publication_status`` is set on the model with Draft or Published\n        options.\n        \"\"\"\n        DRAFT = 'd'\n        PUBLISHED = 'p'\n\n        PUBLICATION_STATUS_CHOICES = (\n            (DRAFT, 'Draft'),\n            (PUBLISHED, 'Published'),\n        )\n\n        publication_status = models.CharField(\n            \"Publication Status\", max_length=1,\n            choices=PUBLICATION_STATUS_CHOICES, default=DRAFT)\n\n        class Meta:\n            abstract = True\n\n        objects = PublishedQuerySet.as_manager()\n        publications = PublishedQuerySet.as_manager()\n\n        @property\n        def draft(self):\n            return self.publication_status == self.DRAFT\n\n        @property\n        def published(self):\n            return self.publication_status == self.PUBLISHED\n\nThe 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.\n\n.. code-block:: python\n\n    # models.py\n    from behaviors.behaviors import Published\n\n\n    class MyModel(Published):\n        name = models.CharField(max_length=100)\n\n    \u003e\u003e\u003e m = MyModel.objects.create(name='cj')\n    \u003e\u003e\u003e m.publication_status\n    u'd'\n    \u003e\u003e\u003e m.draft\n    True\n    \u003e\u003e\u003e m.published\n    False\n    \u003e\u003e\u003e m.get_publication_status_display()\n    u'Draft'\n    \u003e\u003e\u003e MyModel.objects.published().count()\n    0\n    \u003e\u003e\u003e MyModel.objects.draft().count()\n    1\n    \u003e\u003e\u003e m.publication_status = MyModel.PUBLISHED\n    \u003e\u003e\u003e m.save()\n    \u003e\u003e\u003e m.publication_status\n    u'p'\n    \u003e\u003e\u003e m.draft\n    False\n    \u003e\u003e\u003e m.published\n    True\n    \u003e\u003e\u003e m.get_publication_status_display()\n    u'Published'\n    \u003e\u003e\u003e MyModel.objects.published().count()\n    1\n    \u003e\u003e\u003e MyModel.PUBLISHED\n    u'p'\n    \u003e\u003e\u003e MyModel.PUBLISHED == m.publication_status\n    True\n\nThe ``publication_status`` field defaults to ``Published.DRAFT`` when you make new\nmodels unless you supply the ``Published.PUBLISHED`` attribute to the ``publication_status``\nfield.\n\n.. code-block:: python\n\n    MyModel.objects.create(name='Jim-bob Cooter', publication_status=MyModel.PUBLISHED)\n\nPublished QuerySet\n...................\n\nThe ``Published`` behavior attaches to the default ``objects`` variable and\nthe ``publications`` variable as a fallback if ``objects`` is overrode.\n\n.. code-block:: python\n\n    # returns all MyModel.PUBLISHED\n    MyModel.objects.published()\n    MyModel.publications.published()\n\n    # returns all MyModel.DRAFT\n    MyModel.objects.draft()\n    MyModel.publications.draft()\n\n\nReleased Behavior\n``````````````````\n\nThe ``Released`` behavior adds a field ``release_date`` to your model. The field\nis **not_required**. The release date can be set with the ``release_on(datetime)`` method.\n\n.. code-block:: python\n\n    class Released(models.Model):\n        \"\"\"\n        An abstract behavior representing a release_date for a model to\n        indicate when it should be listed publically.\n        \"\"\"\n        release_date = models.DateTimeField(null=True, blank=True)\n\n        class Meta:\n            abstract = True\n\n        objects = ReleasedQuerySet.as_manager()\n        releases = ReleasedQuerySet.as_manager()\n\n        def release_on(self, date=None):\n            if not date:\n                date = timezone.now()\n            self.release_date = date\n            self.save()\n\n        @property\n        def released(self):\n            return self.release_date and self.release_date \u003c timezone.now()\n\nThere 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.\n\nHere is an example of using the behavior:\n\n.. code-block:: python\n\n    # models.py\n    from django.utils import timezone\n    from datetime import timedelta\n    from behaviors.behaviors import Released\n\n\n    class MyModel(Released):\n        name = models.CharField(max_length=100)\n\n    \u003e\u003e\u003e m = MyModel.objects.create(name='rj')\n    \u003e\u003e\u003e m.release_date\n    None\n    \u003e\u003e\u003e MyModel.objects.no_release_date().count()\n    1\n    \u003e\u003e\u003e m.release_on()\n    \u003e\u003e\u003e MyModel.objects.no_release_date().count()\n    0\n    \u003e\u003e\u003e MyModel.objects.released().count()\n    1\n    \u003e\u003e\u003e m.release_on(timezone.now() + timedelta(weeks=1))\n    \u003e\u003e\u003e MyModel.objects.not_released().count()\n    1\n    \u003e\u003e\u003e MyModel.objects.released().count()\n    0\n\nThe ``release_on`` method defaults to the current time so that the object is immediately\nreleased. 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.\n\nYou can always provide a ``release_date`` on object creation:\n\n.. code-block:: python\n\n    MyModel.objects.create(name='Jim-bob Cooter', release_date=timezone.now())\n\n\nReleased QuerySet\n...................\n\nThe ``Released`` behavior attaches to the default ``objects`` variable and\nthe ``releases`` variable as a fallback if ``objects`` is overrode.\n\n.. code-block:: python\n\n    # returns all not released MyModel objects\n    MyModel.objects.not_released()\n    MyModel.releases.not_released()\n\n    # returns all released MyModel objects\n    MyModel.objects.released()\n    MyModel.releases.released()\n\n    # returns all null release date MyModel objects\n    MyModel.objects.no_release_date()\n    MyModel.releases.no_release_date()\n\nSlugged Behavior\n``````````````````\n\nThe ``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.\n\n**The** ``slug_source`` **property has no set default, you must add it to your model for the behavior to work.**\n\n.. code-block:: python\n\n    class Slugged(models.Model):\n        \"\"\"\n        An abstract behavior representing adding a slug (by default, unique) to\n        a model based on the slug_source property.\n        \"\"\"\n        slug = models.SlugField(\n            max_length=255,\n            unique=BehaviorsConfig.are_slug_unique(),\n            blank=True)\n\n        class Meta:\n            abstract = True\n\n        def save(self, *args, **kwargs):\n            if not self.slug:\n                self.slug = self.generate_unique_slug() \\\n                    if BehaviorsConfig.are_slug_unique() else self.get_slug()\n            super(Slugged, self).save(*args, **kwargs)\n\n        def get_slug(self):\n            return slugify(getattr(self, \"slug_source\"), to_lower=True)\n\n        def is_unique_slug(self, slug):\n            qs = self.__class__.objects.filter(slug=slug)\n            return not qs.exists()\n\n        def generate_unique_slug(self):\n            slug = self.get_slug()\n            new_slug = slug\n\n            iteration = 1\n            while not self.is_unique_slug(new_slug):\n                new_slug = \"%s-%d\" % (slug, iteration)\n                iteration += 1\n\n            return new_slug\n\nThe ``slug`` uses the awesome-slugify package which will preserve unicode\ncharacter slugs. By default, the ``slug`` must be unique and is guaranteed to\nbe unique by the class appending a number ``-[0-9+]`` to the end of the slug\nif it is not unique. The ``unique`` field type `adds an index`_ to the ``slug`` field.\n\nAdd the ``slug_source`` property to your class when mixing in the behavior.\n\nTo allow non-unique slugs, add ``UNIQUE_SLUG_BEHAVIOR = False`` to your project's settings.\n\n.. code-block:: python\n\n    # models.py\n    from behaviors.behaviors import Slugged\n\n\n    class MyModel(Slugged):\n        name = models.CharField(max_length=100)\n\n        # slug_source is required for the slug to be set\n        @property\n        def slug_source(self):\n          return \"prepended-text-for-fun-{}\".format(self.name)\n\n        # you can now use the slug for your get_absolute_url() method\n        def get_absolute_url(self):\n          return reverse('myapp:mymodel_detail', args=[self.slug])\n\n    \u003e\u003e\u003e m = MyModel.objects.create(name='aj')\n    \u003e\u003e\u003e m.slug\n    'prepended-text-for-fun-aj'\n    \u003e\u003e\u003e m2 = MyModel.objects.create(name='aj')\n    \u003e\u003e\u003e m.slug\n    'prepended-text-for-fun-aj-1'\n    \u003e\u003e\u003e m.get_absolute_url()\n    '/myapp/prepended-text-for-fun-aj/detail'\n\nYour ``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``.\nBy default, the ``slug`` is unique so it can be used to define the ``get_absolute_url()`` method on your model.\n\nThanks to @apirobot for sending the PR for the ``Slugged`` behavior.\n\nMixing in with Custom Managers\n------------------------------\n\nIf you have a custom manager on your model already:\n\n.. code-block:: python\n\n    # models.py\n    from behaviors.behaviors import Authored, Editored, Published, Timestamped\n\n    from django.db import models\n\n\n    class MyModelCustomManager(models.Manager):\n\n        def get_queryset(self):\n            return super(MyModelCustomManager).get_queryset(self)\n\n        def custom_manager_method(self):\n            return self.get_queryset().filter(name='Jim-bob')\n\n    class MyModel(Authored):\n        name = models.CharField(max_length=100)\n\n        # MyModel.objects.authored_by(..) won't work\n        # MyModel.authors.authored_by(..) still will\n        objects = MyModelCustomManager()\n\nSimply add ``AuthoredManager`` from ``behaviors.managers`` as a mixin to\n``MyModelCustomManager`` so they can share the ``objects`` variable.\n\n.. code-block:: python\n\n    # models.py\n    from behaviors.behaviors import Authored, Editored, Published, Timestamped\n    from behaviors.managers import AuthoredManager, EditoredManager, PublishedManager\n\n    from django.db import models\n\n\n    class MyModelCustomManager(AuthoredManager, models.Manager):\n\n        def get_queryset(self):\n            return super(MyModelCustomManager).get_queryset(self)\n\n        def custom_manager_method(self):\n            return self.get_queryset().filter(name='Jim-bob')\n\n    class MyModel(Authored):\n        name = models.CharField(max_length=100)\n\n        # MyModel.objects.authored_by(..) now works\n        objects = MyModelCustomManager()\n\nSimilarly if you are using a custom QuerySet and calling its ``as_manager()``\nmethod to attach it to ``objects`` you can import from ``behaviors.querysets``\nand mix it in.\n\n.. code-block:: python\n\n    # models.py\n    from behaviors.behaviors import Authored, Editored, Published, Timestamped\n    from behaviors.querysets import AuthoredQuerySet, EditoredQuerySet, PublishedQuerySet\n\n    from django.db import models\n\n\n    class MyModelCustomQuerySet(AuthoredQuerySet, models.QuerySet):\n\n        def custom_queryset_method(self):\n            return self.filter(name='Jim-bob')\n\n    class MyModel(Authored):\n        name = models.CharField(max_length=100)\n\n        # MyModel.objects.authored_by(..) works\n        objects = MyModelCustomQuerySet.as_manager()\n\n\nMixing in Multiple Behaviors\n----------------------------\n\nMany times you will want multiple behaviors on a model. You can simply mix in\nmultiple behaviors and, if you'd like to have all their custom ``QuerySet``\nmethods work on ``objects``, provide a custom manager with all the mixins.\n\n.. code-block:: python\n\n    # models.py\n    from behaviors.behaviors import Authored, Editored, Published, Timestamped\n    from behaviors.querysets import AuthoredQuerySet, EditoredQuerySet, PublishedQuerySet\n\n    from django.db import models\n\n\n    class MyModelQuerySet(AuthoredQuerySet, EditoredQuerySet, PublishedQuerySet):\n        pass\n\n    class MyModel(Authored, Editored, Published, Timestamped):\n        name = models.CharField(max_length=100)\n\n        # MyModel.objects.authored_by(..) works\n        # MyModel.objects.edited_by(..) works\n        # MyModel.objects.published() works\n        # MyModel.objects.draft() works\n        objects = MyModelQuerySet.as_manager()\n\n    # you can also chain queryset methods\n    \u003e\u003e\u003e u = User.objects.all()[0]\n    \u003e\u003e\u003e u2 = User.objects.all()[1]\n    \u003e\u003e\u003e m = MyModel.objects.create(author=u, editor=u2)\n    \u003e\u003e\u003e MyModel.objects.published().authored_by(u).count()\n    1\n\n\nRunning Tests\n-------------\n\nDoes the code actually work?\n\n::\n\n    source \u003cYOURVIRTUALENV\u003e/bin/activate\n    (myenv) $ pip install tox\n    (myenv) $ tox\n\nCredits\n-------\n\nTools used in rendering this package:\n\n*  Cookiecutter_\n*  `cookiecutter-djangopackage`_\n\n.. _Cookiecutter: https://github.com/audreyr/cookiecutter\n.. _`cookiecutter-djangopackage`: https://github.com/pydanny/cookiecutter-djangopackage\n\n.. _`Timestamped`: #timestamped-behavior\n.. _`StoreDeleted`: #storedeleted-behavior\n.. _`Authored`: #authored-behavior\n.. _`Editored`: #editored-behavior\n.. _`Published`: #published-behavior\n.. _`Released`: #released-behavior\n.. _`Slugged`: #slugged-behavior\n.. _`settings.AUTH_USER_MODEL`: https://docs.djangoproject.com/en/1.10/ref/settings/#std:setting-AUTH_USER_MODEL\n.. _`Mixing in with Custom Managers`: #mixing-in-with-custom-managers\n.. _`Mixing Multiple Behaviors`: #mixing-in-multiple-behaviors\n.. _`Django Model Behaviors`: http://blog.kevinastone.com/django-model-behaviors.html\n.. _`adds an index`: https://docs.djangoproject.com/en/dev/ref/models/fields/#unique\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faudiolion%2Fdjango-behaviors","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Faudiolion%2Fdjango-behaviors","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Faudiolion%2Fdjango-behaviors/lists"}