Skip to content

Commit 50174d1

Browse files
committed
[celery#305]: Improving abstract models implementation.
Added a `helpers` module into `models` containing the functions `taskresult_model()` and `groupresult_model()`. * `taskresult_model()`: will try to find the custom model using a dotted path defined under the constant `CELERY_RESULTS_TASKRESULT_MODEL` in the settings of the user's project * `groupresult_model()` will try to do the same using under the constant `CELERY_RESULTS_GROUPRESULT_MODEL`. By default if these attributes are not found `django-celery-results` will use the default models (`models.TaskResult` and `models.GroupResult`). Updated database backend in order to use custom models for `TaskResult and `GroupResult` it they're present. Instead to import explicitely the `TaskResult` and the `GroupResult` (default models from `django-celery-results`) we make use of the model helpers to load the right classes, the custom ones if they're present otherwise we use the default ones. Getting data from `task_kwargs` to extend the `task_properties` and be able to store them into the database (using the custom models). First of all we need a way to get data from `task_kwargs` (or somewhere else) just before a `task_result` record is created, evaluate that data and find the right values that will be used to fill the new fields defined in the custom model. So for this purpose we added a settings module to `django-celery-results` which will hold default settings, the first setting that will contain is a function in charge to get a callback from the settings of the user project. This callback will be feeded by the task `task_kwargs`, which will be intercepted in `DatabaseBackend._get_extended_properties` just before the `task_kwargs` are encoded by `encode_content()` method and send it to the `store_result` method from the object manager of `TaskModel` (Custom/Default one). To end, we must to extend the arguments of the `store_result` method from the `TaskResult` Manager adding `**extra_fields` that will make us able to send extra data to the custom model, when it's defined. --- Resolves celery#305 Fixes celery#314
1 parent 7222185 commit 50174d1

File tree

8 files changed

+88
-9
lines changed

8 files changed

+88
-9
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,4 @@ cover/
3030
.cache/
3131
htmlcov/
3232
coverage.xml
33+
.vscode

django_celery_results/admin.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
ALLOW_EDITS = False
1111
pass
1212

13-
from .models import GroupResult, TaskResult
13+
from .models.helpers import taskresult_model, groupresult_model
14+
15+
GroupResult = groupresult_model()
16+
TaskResult = taskresult_model()
1417

1518

1619
class TaskResultAdmin(admin.ModelAdmin):

django_celery_results/backends/database.py

+8-4
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
from kombu.exceptions import DecodeError
1313

1414
from ..models import ChordCounter
15-
from ..models import GroupResult as GroupResultModel
16-
from ..models import TaskResult
15+
from ..models.helpers import taskresult_model, groupresult_model
16+
from ..settings import extend_task_props_callback
1717

1818
EXCEPTIONS_TO_CATCH = (InterfaceError,)
1919

@@ -29,8 +29,8 @@
2929
class DatabaseBackend(BaseDictBackend):
3030
"""The Django database backend, using models to store task state."""
3131

32-
TaskModel = TaskResult
33-
GroupModel = GroupResultModel
32+
TaskModel = taskresult_model()
33+
GroupModel = groupresult_model()
3434
subpolling_interval = 0.5
3535

3636
def exception_safe_to_retry(self, exc):
@@ -79,6 +79,10 @@ def _get_extended_properties(self, request, traceback):
7979
# task protocol 1
8080
task_kwargs = getattr(request, 'kwargs', None)
8181

82+
# TODO: We assuming that task protocol 1 could be always in use. :/
83+
extended_props.update(
84+
extend_task_props_callback(getattr(request, 'kwargs', None)))
85+
8286
# Encode input arguments
8387
if task_args is not None:
8488
_, _, task_args = self.encode_content(task_args)

django_celery_results/models/managers.py django_celery_results/managers.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from django.conf import settings
99
from django.db import connections, models, router, transaction
1010

11-
from ..utils import now, raw_delete
11+
from .utils import now, raw_delete
1212

1313
W_ISOLATION_REP = """
1414
Polling results with transaction isolation level 'repeatable-read'
@@ -119,7 +119,7 @@ def store_result(self, content_type, content_encoding,
119119
traceback=None, meta=None,
120120
periodic_task_name=None,
121121
task_name=None, task_args=None, task_kwargs=None,
122-
worker=None, using=None):
122+
worker=None, using=None, **extra_fields):
123123
"""Store the result and status of a task.
124124
125125
Arguments:
@@ -140,6 +140,7 @@ def store_result(self, content_type, content_encoding,
140140
exception (only passed if the task failed).
141141
meta (str): Serialized result meta data (this contains e.g.
142142
children).
143+
**extra_fields (dict): Extra (model)fields to store.
143144
144145
Keyword Arguments:
145146
exception_retry_count (int): How many times to retry by
@@ -159,7 +160,8 @@ def store_result(self, content_type, content_encoding,
159160
'task_name': task_name,
160161
'task_args': task_args,
161162
'task_kwargs': task_kwargs,
162-
'worker': worker
163+
'worker': worker,
164+
**extra_fields
163165
}
164166
obj, created = self.using(using).get_or_create(task_id=task_id,
165167
defaults=fields)

django_celery_results/models/abstract.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from django.db import models
66
from django.utils.translation import gettext_lazy as _
77

8-
from . import managers
8+
from .. import managers
99

1010
ALL_STATES = sorted(states.ALL_STATES)
1111
TASK_STATE_CHOICES = sorted(zip(ALL_STATES, ALL_STATES))

django_celery_results/models/generic.py

+6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ class Meta(AbstractTaskResult.Meta):
2121
"""Table information."""
2222

2323
abstract = False
24+
app_label = "django_celery_results"
2425

2526

2627
class ChordCounter(models.Model):
@@ -48,6 +49,10 @@ class ChordCounter(models.Model):
4849
)
4950
)
5051

52+
class Meta:
53+
app_label = "django_celery_results"
54+
55+
5156
def group_result(self, app=None):
5257
"""Return the GroupResult of self.
5358
@@ -71,3 +76,4 @@ class Meta(AbstractGroupResult.Meta):
7176
"""Table information."""
7277

7378
abstract = False
79+
app_label = "django_celery_results"
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from django.apps import apps
2+
from django.conf import settings
3+
from django.core.exceptions import ImproperlyConfigured
4+
5+
from .generic import TaskResult, GroupResult
6+
7+
def taskresult_model():
8+
"""Return the TaskResult model that is active in this project."""
9+
if not hasattr(settings, 'CELERY_RESULTS_TASKRESULT_MODEL'):
10+
return TaskResult
11+
12+
try:
13+
return apps.get_model(
14+
settings.CELERY_RESULTS_TASKRESULT_MODEL
15+
)
16+
except ValueError:
17+
raise ImproperlyConfigured(
18+
"CELERY_RESULTS_TASKRESULT_MODEL must be of the form "
19+
"'app_label.model_name'"
20+
)
21+
except LookupError:
22+
raise ImproperlyConfigured(
23+
"CELERY_RESULTS_TASKRESULT_MODEL refers to model "
24+
f"'{settings.CELERY_RESULTS_TASKRESULT_MODEL}' that has not "
25+
"been installed"
26+
)
27+
28+
def groupresult_model():
29+
"""Return the GroupResult model that is active in this project."""
30+
if not hasattr(settings, 'CELERY_RESULTS_GROUPRESULT_MODEL'):
31+
return GroupResult
32+
33+
try:
34+
return apps.get_model(
35+
settings.CELERY_RESULTS_GROUPRESULT_MODEL
36+
)
37+
except ValueError:
38+
raise ImproperlyConfigured(
39+
"CELERY_RESULTS_GROUPRESULT_MODEL must be of the form "
40+
"'app_label.model_name'"
41+
)
42+
except LookupError:
43+
raise ImproperlyConfigured(
44+
"CELERY_RESULTS_GROUPRESULT_MODEL refers to model "
45+
f"'{settings.CELERY_RESULTS_GROUPRESULT_MODEL}' that has not "
46+
"been installed"
47+
)

django_celery_results/settings.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from django.conf import settings
2+
3+
4+
def get_callback_function(settings_name, default=None):
5+
"""Return the callback function for the given settings name."""
6+
7+
callback = getattr(settings, settings_name, None)
8+
if callback is None:
9+
return default
10+
11+
if callable(callback):
12+
return callback
13+
14+
extend_task_props_callback = get_callback_function(
15+
"CELERY_RESULTS_EXTEND_TASK_PROPS_CALLBACK"
16+
)

0 commit comments

Comments
 (0)