Package: django-tenants-celery-beat
django-tenants-celery-beatSupport for celery beat in multitenant Django projects. Schedule periodic tasks for a
specific tenant, with flexibility to run tasks with respect to each tenant's timezone.For use withdjango-tenantsandtenant-schemas-celery.Features:Configure static periodic tasks inapp.conf.beat_scheduleautomatically for all
tenants, optionally in their own timezonesDjango admin modified to show and give you control over the tenant a task will run inFilter the admin based on tenantsTenant-level admin (e.g. tenant.domain.com) will only show tasks for that tenantInstallationInstall via pip:pip install django-tenants-celery-beatUsageFollow the instructions fordjango-tenantsandtenant-schemas-celery.In yourSHARED_APPS(notyourTENANT_APPS):SHARED_APPS=[# ..."django_celery_results","django_celery_beat","django_tenants_celery_beat",# ...]Depending on your setup, you may also putdjango_celery_resultsin yourTENANT_APPS.
(Assuming you have followed the instructions fordjango-tenantsall yourSHARED_APPSwill also appear in yourINSTALLED_APPS.)django-tenants-celery-beatrequires yourTenantmodel to have atimezonefield in
order to control periodic task scheduling. To this end, we provide aTenantTimezoneMixinthat you should inherit from in yourTenantmodel, e.g.:fromdjango_tenants.modelsimportTenantMixinfromdjango_tenants_celery_beat.modelsimportTenantTimezoneMixinclassTenant(TenantTimezoneMixin,TenantMixin):passYou can configure whether the timezones are displayed with the GMT offset, i.e.Australia/Sydneyvs.GMT+11:00 Australia/Sydney, using the settingTENANT_TIMEZONE_DISPLAY_GMT_OFFSET. By default, the GMT offset is not shown.
(If you later change this setting, you will need to runmakemigrationsto see any effect.)Ensure thatDJANGO_CELERY_BEAT_TZ_AWAREis True (the default) for any timezone aware
scheduling to work.In order to make the link between yourTenantmodel andPeriodicTask, the app comes
with an abstract model. You simply need create a class that inherits from this mixin and
does nothing else. Having this model in your own first-party app means that the migrations
can be managed properly.fromdjango_tenants_celery_beat.modelsimportPeriodicTaskTenantLinkMixinclassPeriodicTaskTenantLink(PeriodicTaskTenantLinkMixin):passYou need to register which model is acting as the link. If your tenancy models live in
an app calledtenancyand the model is named as above, you need the following in your
project settings:PERIODIC_TASK_TENANT_LINK_MODEL="tenancy.PeriodicTaskTenantLink"Once this has been done, you will need to runmakemigrations. This will create the
necessary migrations for yourTenantandPeriodicTaskTenantLinkmodels.
To apply the migrations, run:python manage.py migrate_schemas --sharedSetting up abeat_scheduleFor statically configured periodic tasks assigned viaapp.conf.beat_schedule, there
is a helper utility function to produce a valid tenant-awarebeat_schedule. You can take
an existingbeat_scheduleand make minor modifications to achieve the desired behaviour.Thegenerate_beat_schedulefunction takes a dict that looks exactly like the usualbeat_scheduledict, but each task contains an additional entry with the keytenancy_options.
Here you can specify three things:Should the task run in thepublicschema?Should the task run on all tenant schemas?Should the task scheduling use the tenant's timezone?All of these are False by default, so you only need to include them if you set them to True,
though you may prefer to keep them there to be explicit about your intentions. At least one
of thepublicorall_tenantskeys must be True, otherwise the entry is ignored.
Additionally, if thetenancy_optionkey is missing from an entry, that entry will be ignored.Example usage:app.conf.beat_schedule=generate_beat_schedule({"tenant_task":{"task":"app.tasks.tenant_task","schedule":crontab(minute=0,hour=12,day_of_week=1),"tenancy_options":{"public":False,"all_tenants":True,"use_tenant_timezone":True,}},"hourly_tenant_task":{"task":"app.tasks.hourly_tenant_task","schedule":crontab(minute=0),"tenancy_options":{"public":False,"all_tenants":True,"use_tenant_timezone":False,}},"public_task":{"task":"app.tasks.tenant_task","schedule":crontab(minute=0,hour=0,day_of_month=1),"tenancy_options":{"public":True,"all_tenants":False,}}})Thisbeat_schedulewill actually produce an entry for each tenant with the schema name
as a prefix. For example,tenant1: celery.backend_cleanup. For public tasks, there is
no prefix added to the name.This function also sets some AMQP message headers, which is how the schema and timezone
settings are configured.Configuringcelery.backend_cleanupNote that in many cases, tasks should not be both run on thepublicschema and on all
tenant schemas, as the database tables are often very different. One example that most
likely should is thecelery.backend_cleanuptask that is automatically added. If you
do nothing with it, it will run only in the public schema, which may or may not suit your
needs. Assuming you havedjango_celery_resultsinTENANT_APPSyou will need this task to
be run on all tenants, and if you also have it inSHARED_APPS, you will need it to run
on thepublicschema too. This task is also a case where you will likely want it to run
in the tenant's timezone so it always runs during a quiet time.Using the utility function, this is how we could set up thecelery.backend_cleanuptask:fromdjango_tenants_celery_beat.utilsimportgenerate_beat_schedule# ...app.conf.beat_schedule=generate_beat_schedule({"celery.backend_cleanup":{"task":"celery.backend_cleanup","schedule":crontab("0","4","*"),"options":{"expire_seconds":12*3600},"tenancy_options":{"public":True,"all_tenants":True,"use_tenant_timezone":True,}}})This will prevent the automatically created one being added, though the settings are
identical to the automatic one as ofdjango-celery-beat==2.2.0. You could also setpublicto False here for exactly the same resulting schedule, as the public one will
be automatically created bydjango-celery-beat.Modifying Periodic Tasks in the Django AdminYou can further manage periodic tasks in the Django admin.The public schema admin will display the periodic tasks for each tenant as well as the
public tenant.When on a tenant-level admin (e.g.tenant.domain.com), you can only see
the tasks for the given tenant, and any filters are hidden so as to not show a list of
tenants.When editing aPeriodicTask, there is an inline form for theOneToOneModeladded by
this package that connects aPeriodicTaskto aTenant. You can toggle theuse_tenant_timezonesetting (but when restarting the beat service, thebeat_schedulewill always take precedence). The tenant is shown as a read-only field, unless you are
on the public admin site, in which case you have the option edit the tenant. Editing the
tenant here will take precedence over thebeat_schedule.Developer SetupTo set up the example app:Navigate into theexampledirectoryCreate a virtual environment and install the requirements inrequirements.txtCreate a postgres database according to theexample.settings.DATABASES["default"](edit the settings if necessary)Runpython manage.py migrate_schemasto create the public schemaRunpython manage.py create_tenantto create the public tenant and any other tenantsCreate superusers withpython manage.py create_tenant_superuserRuncelery -A example beat --loglevel=INFOto run the beat schedulerRuncelery -A example worker --loglevel=INFO(add--pool=soloif on Windows)