-
Notifications
You must be signed in to change notification settings - Fork 464
Abstract Django DB Models #911
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Model classes moved into a `models.py` directory, which exports by default only the default `django-celery-beat` model classes now from `models.generic.py`. `managers.py` moved into `models.py` directory, because it's something that is needed only by models.
`crontab_schedule_celery_timezone` wasn't exported properly and raised an `AttributeError` when applying migrations.
…ry#516) when `CELERY_BEAT_(?:PERIODICTASKS?|(?:CRONTAB|INTERVAL|SOLAR|CLOCKED)SCHEDULE)_MODEL` constants are defined in django `settings`. Providing the `app_label.model_name` of your own models as value for the constants `CELERY_BEAT_(?:PERIODICTASKS?|(?:CRONTAB|INTERVAL|SOLAR|CLOCKED)SCHEDULE)_MODEL` will let `django_celery_beat.scheduler` use the custom models instead of the default ones (aka generic models, in this context/pull-request): ```python CELERY_BEAT_PERIODICTASK_MODEL = "app_label.model_name" CELERY_BEAT_PERIODICTASKS_MODEL = "app_label.model_name" CELERY_BEAT_CRONTABSCHEDULE_MODEL = "app_label.model_name" CELERY_BEAT_INTERVALSCHEDULE_MODEL = "app_label.model_name" CELERY_BEAT_SOLARSCHEDULE_MODEL = "app_label.model_name" CELERY_BEAT_CLOCKEDSCHEDULE_MODEL = "app_label.model_name" ``` Doing this we add support to automatically bypass the default `django_celery_beat` models without forcing developers to overwrite the whole `django_celery_beat.scheduler` in projects where the default models doesn't fit the requirements I updated the `README.rst` with a small explanation about how this work Additonal information: * related issue: celery#516 * pull-request: celery#534
…nto feature/abstract-models-import
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #911 +/- ##
==========================================
+ Coverage 88.19% 89.35% +1.16%
==========================================
Files 32 35 +3
Lines 1008 1118 +110
Branches 105 117 +12
==========================================
+ Hits 889 999 +110
Misses 101 101
Partials 18 18 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Is there any progress or input on this? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the Django Celery Beat models to be fully abstract and configurable via settings, allowing each service to use its own database tables. Key changes include:
- Introduce
helpers.py
with functions to dynamically fetch models based onCELERY_BEAT_*_MODEL
settings. - Convert all core models in
models/abstract.py
to abstract base classes and add concrete implementations inmodels/generic.py
. - Update
schedulers.py
to use dynamic models and add tests & documentation for the new custom-model feature.
Reviewed Changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
t/unit/test_helpers.py | Add tests for helper functions fetching custom and default models |
django_celery_beat/helpers.py | New dynamic model lookup functions based on project settings |
django_celery_beat/schedulers.py | Swap static model imports for dynamic lookup via helpers |
django_celery_beat/models/abstract.py | Convert concrete models to abstract classes; update imports |
django_celery_beat/models/generic.py | Provide concrete implementations inheriting from abstract classes |
django_celery_beat/models/init.py | Expose new generic classes in package API |
README.rst | Document how to use custom abstract models in settings |
Comments suppressed due to low confidence (2)
django_celery_beat/models/generic.py:31
- [nitpick] The
ClockedSchedule
docstring is duplicated fromIntervalSchedule
and mentions intervals. Consider updating it to accurately describe clocked schedules.
"""Schedule with a fixed interval."""
t/unit/test_helpers.py:47
- Using
.upper()
on the model name will generate incorrect class names (e.g.CRONTABSCHEDULE
instead ofCrontabSchedule
). Use the exact class name casing when constructingapp_label.model_name
for lookup.
model_setting_name = f"CELERY_BEAT_{model_name.upper()}_MODEL"
|
||
|
||
class PeriodicTask(AbstractPeriodicTask): | ||
"""Interal task scheduling class.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix the typo in the docstring: change Interal
to Internal
.
"""Interal task scheduling class.""" | |
"""Internal task scheduling class.""" |
Copilot uses AI. Check for mistakes.
|
||
def crontabschedule_model(): | ||
"""Return the CrontabSchedule model that is active in this project.""" | ||
if not hasattr(settings, "CELERY_BEAT_CRONTABSCHEDULE_MODEL"): | ||
from .models.generic import CrontabSchedule | ||
|
||
return CrontabSchedule | ||
|
||
try: | ||
return apps.get_model(settings.CELERY_BEAT_CRONTABSCHEDULE_MODEL) | ||
except ValueError: | ||
raise ImproperlyConfigured( | ||
"CELERY_BEAT_CRONTABSCHEDULE_MODEL must be of the form " | ||
"'app_label.model_name'" | ||
) | ||
except LookupError: | ||
raise ImproperlyConfigured( | ||
"CELERY_BEAT_CRONTABSCHEDULE_MODEL refers to model " | ||
f"'{settings.CELERY_BEAT_CRONTABSCHEDULE_MODEL}' that has not " | ||
"been installed" | ||
) | ||
|
||
|
||
def intervalschedule_model(): | ||
"""Return the IntervalSchedule model that is active in this project.""" | ||
if not hasattr(settings, "CELERY_BEAT_INTERVALSCHEDULE_MODEL"): | ||
from .models.generic import IntervalSchedule | ||
|
||
return IntervalSchedule | ||
|
||
try: | ||
return apps.get_model(settings.CELERY_BEAT_INTERVALSCHEDULE_MODEL) | ||
except ValueError: | ||
raise ImproperlyConfigured( | ||
"CELERY_BEAT_INTERVALSCHEDULE_MODEL must be of the form " | ||
"'app_label.model_name'" | ||
) | ||
except LookupError: | ||
raise ImproperlyConfigured( | ||
"CELERY_BEAT_INTERVALSCHEDULE_MODEL refers to model " | ||
f"'{settings.CELERY_BEAT_INTERVALSCHEDULE_MODEL}' that has not " | ||
"been installed" | ||
) | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The model‐fetching functions in this file have nearly identical logic. Consider extracting the common pattern into a single helper that takes the setting name and default import path to reduce duplication.
def crontabschedule_model(): | |
"""Return the CrontabSchedule model that is active in this project.""" | |
if not hasattr(settings, "CELERY_BEAT_CRONTABSCHEDULE_MODEL"): | |
from .models.generic import CrontabSchedule | |
return CrontabSchedule | |
try: | |
return apps.get_model(settings.CELERY_BEAT_CRONTABSCHEDULE_MODEL) | |
except ValueError: | |
raise ImproperlyConfigured( | |
"CELERY_BEAT_CRONTABSCHEDULE_MODEL must be of the form " | |
"'app_label.model_name'" | |
) | |
except LookupError: | |
raise ImproperlyConfigured( | |
"CELERY_BEAT_CRONTABSCHEDULE_MODEL refers to model " | |
f"'{settings.CELERY_BEAT_CRONTABSCHEDULE_MODEL}' that has not " | |
"been installed" | |
) | |
def intervalschedule_model(): | |
"""Return the IntervalSchedule model that is active in this project.""" | |
if not hasattr(settings, "CELERY_BEAT_INTERVALSCHEDULE_MODEL"): | |
from .models.generic import IntervalSchedule | |
return IntervalSchedule | |
try: | |
return apps.get_model(settings.CELERY_BEAT_INTERVALSCHEDULE_MODEL) | |
except ValueError: | |
raise ImproperlyConfigured( | |
"CELERY_BEAT_INTERVALSCHEDULE_MODEL must be of the form " | |
"'app_label.model_name'" | |
) | |
except LookupError: | |
raise ImproperlyConfigured( | |
"CELERY_BEAT_INTERVALSCHEDULE_MODEL refers to model " | |
f"'{settings.CELERY_BEAT_INTERVALSCHEDULE_MODEL}' that has not " | |
"been installed" | |
) | |
def get_model_or_default(setting_name, default_model_path): | |
"""Fetch a model based on a setting name or return the default model.""" | |
if not hasattr(settings, setting_name): | |
module_path, model_name = default_model_path.rsplit(".", 1) | |
module = __import__(module_path, fromlist=[model_name]) | |
return getattr(module, model_name) | |
try: | |
return apps.get_model(getattr(settings, setting_name)) | |
except ValueError: | |
raise ImproperlyConfigured( | |
f"{setting_name} must be of the form 'app_label.model_name'" | |
) | |
except LookupError: | |
raise ImproperlyConfigured( | |
f"{setting_name} refers to a model " | |
f"'{getattr(settings, setting_name)}' that has not been installed" | |
) | |
def crontabschedule_model(): | |
"""Return the CrontabSchedule model that is active in this project.""" | |
return get_model_or_default( | |
"CELERY_BEAT_CRONTABSCHEDULE_MODEL", | |
"django_celery_beat.models.generic.CrontabSchedule" | |
) | |
def intervalschedule_model(): | |
"""Return the IntervalSchedule model that is active in this project.""" | |
return get_model_or_default( | |
"CELERY_BEAT_INTERVALSCHEDULE_MODEL", | |
"django_celery_beat.models.generic.IntervalSchedule" | |
) |
Copilot uses AI. Check for mistakes.
Hello,
We encountered an issue in our products where multiple Django services share the same database/schema with Django Celery Beat (DCB) installed. In this setup, Service A attempts to execute Service B’s periodic tasks and vice versa, even though they do not share the same codebase. To address this, we aimed to configure DCB to write to separate tables for each service.
I found an implementation in #534 and have updated it to work with the current mainline DCB. This feature was also requested in #904, and @auvipy mentioned it is on the DCB roadmap. Unit tests pass on my local Python environment, though I have not run the full test matrix.
This change has been very helpful for us, allowing us to do the following:
and add the following to our
settings.py
:With this approach, DCB writes to a dedicated table for Product A.
Please let me know if there’s anything I can do to help move this forward or improve the PR for merging.
Thank you!
Best regards,
Brett