Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

A django package for managing subscription states

License

NotificationsYou must be signed in to change notification settings

kogan/django-subscriptions

Repository files navigation

A django package for managing the status and terms of a subscription.

PyPI versionCircleCI (all branches)Code style: blackPyPI - License

Compatibility

  • Django: 2.2 (LTS versions only)
  • Python: 3.6+

Other Django or Python versionsmay work, but that is totally cooincidentaland no effort is made to maintain compatibility with versions other than thoselisted above.

Installation

$ pip install django-subscriptions

Then add the following packages toINSTALLED_APPS in your settings:

INSTALLED_APPS = [    ...    "django_fsm_log",    "subscriptions.apps.SubscriptionsConfig",    ...]

And of course, you'll need to run the migrations:

$ python manage.py migrate

You'll also need to setup the triggers, which can be scheduled with celery orrun from a management task. See theTriggers section below.

Design

Manages subscriptions in a single table. Pushes events (signals) so thatconsumers can do the actual work required for that subscription, like billing.

Subscriptions are built around a Finite State Machine model, where states andallowed transitions between states are well defined on the Model. To update fromone state to another, the user calls methods on the Subscription instance. Thisway, all side-effects and actions are contained within the state methods.

Subscription State must not be modified directly.

When a state change is triggered, the subscription will publish relevant signalsso that interested parties can, themselves, react to the state changes.

State Diagram

API

There are 3 major API components. State change methods, signals/events, and thetriggers used to begin the state changes.

State Methods

MethodSource StatesTarget StateSignal Emitted
cancel_autorenew()ACTIVEEXPIRINGautorenew_canceled
enable_autorenew()EXPIRINGACTIVEautorenew_enabled
renew()ACTIVE,SUSPENDEDRENEWINGsubscription_due
renewed(new_end, new_ref, description=None)ACTIVE,RENEWING,ERRORACTIVEsubscription_renewed
renewal_failed(description=None)RENEWING,ERRORSUSPENDEDrenewal_failed
end_subscription(description=None)ACTIVE,SUSPENDED,EXPIRING,ERRORENDEDsubscription_ended
state_unknown(description=None)RENEWINGERRORsubscription_error

Example:

subscription.renew() may only be called ifsubscription.state is eitherACTIVE orSUSPENDED,and will causesubscription.state to move into theRENEWING state.

Thedescription argument is a string that can be used to persist the reason for a statechange in theStateLog table (and admin inlines).

Triggers

There are a bunch of triggers that are used to update subscriptions as they becomedue or expire. Nothing is configured to run these triggers by default. You caneither call them as part of your own process, or usecelery beat to executethe triggers using the tasks provided insubscriptions.tasks.

Create a new subscription:

Subscription.objects.add_subscription(start_date, end_date, reference) -> Subscription

Trigger subscriptions that are due for renewal:

Subscription.objects.trigger_renewals() -> int  # number of renewals sent

Trigger subscriptions that are due to expire:

Subscription.objects.trigger_expiring() -> int  # number of expirations

Trigger subscriptions that are suspended:

Subscription.objects.trigger_suspended() -> int  # number of renewals

Trigger subscriptions that have been suspended for longer thantimeout_hours toend (usessubscription.end date, notsubscription.last_updated):

Subscription.objects.trigger_suspended_timeout(timeout_hours=48) -> int  # number of suspensions

Trigger subscriptions that have been stuck in renewing state for longer thantimeout_hoursto be marked as an error (usessubscription.last_updated to determine the timeout):

Subscription.objects.trigger_stuck(timeout_hours=2) -> int  # number of error subscriptions

Ifsettings.SUBSCRIPTIONS_STUCK_RETRY isTrue, then subscriptions are moved back intotheSUSPENDED state, ready to be retried. This can be useful when you have an offlineprocess that can resolve stuck subscription issues, and there is no issue retrying thesubscription.

Tasks

The following tasks are defined but are not scheduled:

subscriptions.tasks.trigger_renewalssubscriptions.tasks.trigger_expiringsubscriptions.tasks.trigger_suspendedsubscriptions.tasks.trigger_suspended_timeoutsubscriptions.tasks.trigger_stuck

If you'd like to schedule the tasks, do so with a celery beat configuration like this:

# settings.pyCELERYBEAT_SCHEDULE = {    "subscriptions_renewals": {        "task": "subscriptions.tasks.trigger_renewals",        "schedule": crontab(hour=0, minute=10),    },    "subscriptions_expiring": {        "task": "subscriptions.tasks.trigger_expiring",        "schedule": crontab(hour=0, minute=15),    },    "subscriptions_suspended": {        "task": "subscriptions.tasks.trigger_suspended",        "schedule": crontab(hour="3,6,9", minute=30),    },    "subscriptions_suspended_timeout": {        "task": "subscriptions.tasks.trigger_suspended_timeout",        "schedule": crontab(hour=0, minute=40),        "kwargs": {"hours": 48},    },    "subscriptions_stuck": {        "task": "subscriptions.tasks.trigger_stuck",        "schedule": crontab(hour="*/2", minute=50),        "kwargs": {"hours": 2},    },}

Contributing

We usepre-commit <https://pre-commit.com/> to enforce our code style ruleslocally before you commit them into git. Once you install the pre-commit library(locally via pip is fine), just install the hooks::

pre-commit install -f --install-hooks

The same checks are executed on the build server, so skipping the local linting(withgit commit --no-verify) will only result in a failed test build.

Current style checking tools:

  • flake8: python linting
  • isort: python import sorting
  • black: python code formatting

Note:

You must have python3.6 available on your path, as it is required for someof the hooks.

Generating Migrations

After installing all dependencies, you can generate required migration fileslike so:

$ poetry run ipython migrate.py<nameofmigration>

Publishing a new version

  1. Bump the version number in pyproject.toml and src/subscriptions/init.py
  2. Commit and push to master
  3. From github,create a new release
  4. Name the release "v<maj.minor.patch>" using the version number from step 1.
  5. Publish the release
  6. If the release successfully builds, circleci will publish the new package to pypi

[8]ページ先頭

©2009-2025 Movatter.jp