{"id":20689964,"url":"https://github.com/tj-django/django-model-subscription","last_synced_at":"2025-04-22T16:53:40.105Z","repository":{"id":37695140,"uuid":"213990460","full_name":"tj-django/django-model-subscription","owner":"tj-django","description":"Subscribe to django model changes. Using thread safe subscribers.","archived":false,"fork":false,"pushed_at":"2025-03-31T20:22:20.000Z","size":3746,"stargazers_count":30,"open_issues_count":40,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-07T11:21:35.623Z","etag":null,"topics":["auto-discovery","bulk","django","django-model","django-model-subscribers","django-signals","observer-pattern"],"latest_commit_sha":null,"homepage":"https://django-model-subscription.readthedocs.io/en/latest/installation.html","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/tj-django.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"jackton1","patreon":null,"open_collective":"tj-django","ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":[]}},"created_at":"2019-10-09T18:11:37.000Z","updated_at":"2025-02-26T11:36:39.000Z","dependencies_parsed_at":"2024-05-03T23:24:44.532Z","dependency_job_id":"d11da5ca-b112-4fbc-97ba-ab03d680b381","html_url":"https://github.com/tj-django/django-model-subscription","commit_stats":{"total_commits":681,"total_committers":13,"mean_commits":52.38461538461539,"dds":0.5873715124816447,"last_synced_commit":"1c076021bcdbc46867be14bb177bbe99cf84a0ed"},"previous_names":["jackton1/django-model-subscription"],"tags_count":13,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tj-django%2Fdjango-model-subscription","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tj-django%2Fdjango-model-subscription/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tj-django%2Fdjango-model-subscription/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tj-django%2Fdjango-model-subscription/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tj-django","download_url":"https://codeload.github.com/tj-django/django-model-subscription/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250283256,"owners_count":21405127,"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":["auto-discovery","bulk","django","django-model","django-model-subscribers","django-signals","observer-pattern"],"created_at":"2024-11-16T23:11:26.588Z","updated_at":"2025-04-22T16:53:40.084Z","avatar_url":"https://github.com/tj-django.png","language":"Python","funding_links":["https://github.com/sponsors/jackton1","https://opencollective.com/tj-django","https://www.buymeacoffee.com/jackton1"],"categories":[],"sub_categories":[],"readme":"[![PyPI](https://img.shields.io/pypi/v/django-model-subscription)](https://pypi.org/project/django-model-subscription/) [![Actions Status](https://github.com/jackton1/django-model-subscription/workflows/django%20model%20subscription%20test./badge.svg)](https://github.com/jackton1/django-model-subscription/actions?query=workflow%3A\"django+model+subscription+test.\")\n[![Documentation Status](https://readthedocs.org/projects/django-model-subscription/badge/?version=latest)](https://django-model-subscription.readthedocs.io/en/latest/?badge=latest)\n\n[![Codacy Badge](https://app.codacy.com/project/badge/Grade/353aa86af402423cbcd4e810bca664cc)](https://www.codacy.com/gh/tj-django/django-model-subscription/dashboard?utm_source=github.com\\\u0026utm_medium=referral\\\u0026utm_content=tj-django/django-model-subscription\\\u0026utm_campaign=Badge_Grade) [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/353aa86af402423cbcd4e810bca664cc)](https://www.codacy.com/gh/tj-django/django-model-subscription/dashboard?utm_source=github.com\\\u0026utm_medium=referral\\\u0026utm_content=tj-django/django-model-subscription\\\u0026utm_campaign=Badge_Coverage) [![codecov](https://codecov.io/gh/tj-django/django-model-subscription/branch/master/graph/badge.svg?token=P5X3FM234E)](https://codecov.io/gh/tj-django/django-model-subscription) [![PyPI - License](https://img.shields.io/pypi/l/django-model-subscription.svg)](https://github.com/jackton1/django-model-subscription/blob/master/LICENSE)\n[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/tj-django/django-model-subscription/main.svg)](https://results.pre-commit.ci/latest/github/tj-django/django-model-subscription/main)\n\n[![PyPI - Python Version](https://img.shields.io/pypi/pyversions/django-model-subscription.svg)](https://pypi.org/project/django-model-subscription)\n[![PyPI - Django Version](https://img.shields.io/pypi/djversions/django-model-subscription.svg)](https://docs.djangoproject.com/en/3.2/releases/)\n[![Downloads](https://pepy.tech/badge/django-model-subscription)](https://pepy.tech/project/django-model-subscription)\n\n# django-model-subscription\n\nSubscribe to django model changes include bulk create/update/delete.\n\n## Table of contents\n\n*   [Motivation](#Motivation)\n*   [Installation](#Installation)\n*   [Usage](#Usage)\n    *   [Decorators](#Decorators)\n    *   [Setup Subscribers using AppConfig.ready](#setup-subscribers-using-appconfigready-recomended)\n    *   [Setup Subscribers with auto discovery](#setup-subscribers-using-auto-discovery)\n*   [Credits](#credits)\n*   [Resources](#resources)\n\n### Features\n\n*   Using Observer Pattern notify subscribers about changes to a django model.\n*   Decouple Business logic from Models.save\n*   Support for bulk actions (Not available using django signals.)\n*   Use noop subscribers when `settings.SUBSCRIPTION_DISABLE_SUBSCRIBERS` is `True`\n    which prevents having to mock subscribers that call external services in testing or local development\n    environments.\n*   Show changes to the instance after it has been updated i.e diff's the initial state and the\n    current state.\n\n\u003cimg width=\"580\" alt=\"Subscriber\" src=\"https://user-images.githubusercontent.com/17484350/139741273-83cd6400-552e-419f-8cca-0f13caacf5aa.png\"\u003e\n\n### Installation\n\n```bash\n$ pip install django-model-subscription\n```\n\nAdd `model_subscription` to your INSTALLED\\_APPS\n\n```python\nINSTALLED_APPS = [\n    ...,\n    'model_subscription',\n    ...\n]\n```\n\n### Usage\n\n##### Using the `SubscriptionModelMixin` and `SubscriptionQuerySet`\n\n```py\nfrom model_subscription.mixin import SubscriptionModelMixin\nfrom model_subscription.model import SubscriptionQuerySet\n\n\nclass TestModel(SubscriptionModelMixin, models.Model):\n    name = models.CharField(max_length=255)\n\n    objects = SubscriptionQuerySet.as_manager()\n```\n\n##### Subclassing the `SubscriptionModel` base class.\n\n```py\nfrom model_subscription.model import SubscriptionModel\n\n\nclass TestModel(SubscriptionModel):\n    name = models.CharField(max_length=255)\n\n```\n\n#### Creating subscribers.\n\n*   Using `OperationType`\n\n```python\nimport logging\nfrom model_subscription.decorators import subscribe\nfrom model_subscription.constants import OperationType\n\nlog = logging.getLogger(__name__)\n\n@subscribe(OperationType.CREATE, TestModel)\ndef handle_create(instance):\n    log.debug('Created {}'.format(instance.name))\n\n\n```\n\n*   Using `create_subscription` directly (succinct version).\n\n```python\n\nimport logging\nfrom model_subscription.decorators import create_subscription\n\nlog = logging.getLogger(__name__)\n\n@create_subscription(TestModel)\ndef handle_create(instance):\n    log.debug('Created {}'.format(instance.name))\n\n\n```\n\n### Decorators\n\n*   `subscribe`: Explicit (Requires a valid OperationType).\n\n#### (Create, Update, Delete) operations.\n\n*   `create_subscription`: Subscribes to create operation i.e a new instance.\n\n```python\n@create_subscription(TestModel)\ndef handle_create(instance):\n    log.debug('1. Created {}'.format(instance.name))\n```\n\n*   `update_subscription`: Subscribes to updates also includes (`changed_data`).\n\n```python\n@update_subscription(TestModel)\ndef handle_update(instance, changed_data):\n    log.debug('Updated {} {}'.format(instance.name, changed_data))\n```\n\n*   `delete_subscription`: Subscribes to delete operation:\n\n\u003e NOTE: The instance.pk is already set to None.\n\n```python\n@delete_subscription(TestModel)\ndef handle_delete(instance):\n    log.debug('Deleted {}'.format(instance.name))\n```\n\n#### (Bulk Create, Bulk Update, Bulk Delete) operations.\n\n*   `bulk_create_subscription`: Subscribe to bulk create operations.\n\n```python\n\n@bulk_create_subscription(TestModel)\ndef handle_bulk_create(instances):\n    for instance in instances:\n        log.debug('Bulk Created {}'.format(instance.name))\n\n```\n\n*   `bulk_update_subscription`: Subscribe to bulk update operations.\n\n```python\n@bulk_update_subscription(TestModel)\ndef handle_bulk_update(instances):\n    for instance in instances:\n        log.debug('Updated {}'.format(instance.name))\n```\n\n*   `bulk_delete_subscription`: Subscribe to bulk delete operations.\n\n```python\n\n@bulk_delete_subscription(TestModel)\ndef handle_bulk_delete(instances):\n    for instance in instances:\n        log.debug('Deleted {}'.format(instance.name))\n\n```\n\n### Setup Subscribers using AppConfig.ready `(Recomended)`.\n\nUpdate you `apps.py`\n\n```python\n\nfrom django.apps import AppConfig\n\n\nclass MyAppConfig(AppConfig):\n    name = 'myapp'\n\n    def ready(self):\n        from myapp import subscriptions\n\n```\n\n### Setup Subscribers using auto discovery.\n\nBy default the `settings.SUBSCRIPTION_AUTO_DISCOVER` is set to `False`.\n\nTo use auto discovery this is not recommended as it would notify the subscribers\nwherever the model is used i.e IPython notebook, external scripts.\n\nIn your `settings.py` add\n\n```python\n\nSUBSCRIPTION_AUTO_DISCOVER = True\n\n```\n\n#### Setting up the `SUBSCRIPTION_MODULE`\n\n\u003e NOTE: This is only required when `SUBSCRIPTION_AUTO_DISCOVER = True`\n\n```python\n\nSUBSCRIPTION_MODULE  = 'subscription'\n```\n\n#### Credits\n\n*   [django-lifecycle](https://github.com/rsinger86/django-lifecycle)\n\nIf you feel generous and want to show some extra appreciation:\n\n[![Buy me a coffee][buymeacoffee-shield]][buymeacoffee]\n\n[buymeacoffee]: https://www.buymeacoffee.com/jackton1\n\n[buymeacoffee-shield]: https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png\n\n#### Resources\n\n*   https://python-3-patterns-idioms-test.readthedocs.io/en/latest/Observer.html\n*   https://refactoring.guru/design-patterns/observer\n*   https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c\n\n### TODO's\n\n*   Supporting field level subscriptions.\n*   Support class based subscribers which implements `__call__`\n*   Extend to include custom OperationType.\n*   Add support for using a single class to manage multiple actions i.e MyClass.update, MyClass.create.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftj-django%2Fdjango-model-subscription","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftj-django%2Fdjango-model-subscription","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftj-django%2Fdjango-model-subscription/lists"}