patternpythondjangoMinor
Abstracting celery tasks with class based implementations
Viewed 0 times
implementationswithcelerytasksbasedabstractingclass
Problem
Background
I want to send emails with a rendered template (django template, that is) but I also want to be able to control the QuerySets, and context provided.
The goal is to make a task whose
First pass
The first approach was to define a task for accepting serializable data with rules about how to grab objects from the database - if there was only one value then it was taken as a plain value and passed along to the template context. This lead to having tasks that required a lot of knowledge of how this stuff worked so I decided a level of abstraction was appropriate for keeping the code more concise and readable.
Full Code
```
# -- coding: utf-8 --
from __future__ import unicode_literals
import logging
from django.core.mail import EmailMultiAlternatives as EmailMulti
from django.template.loader import render_to_string
from project.celery import app
logger = logging.getLogger(__name__)
class TemplateEmailTask(app.Task):
'''
Abstract base task for sending an email with one or more templates.
Implementors should define
1. get_context_data, a method like Django Views that should provide a
dictionary for passing to the template renderer,
2. a name class attribute to uniquely identify this task to Celery, and
3. a list of template names to use in rendering the email.
An example implementation would be:
class MyEmail(TemplateEmailTask):
name = 'python.module.path.to.tasks.my_email'
template_names = []
def get_context_data(self, **kwargs):
return {
today: timezone.now()
}
It is totally allowed to make database calls in get_context_data (that's
why it was built this way) to provide data to the template.
Note: it is not
I want to send emails with a rendered template (django template, that is) but I also want to be able to control the QuerySets, and context provided.
The goal is to make a task whose
run method is the same (because sending an email isn't the interesting bit) for each implementation but allow the user to provide data and templates as a configuration for what the run method should feed into the template and mailer.First pass
The first approach was to define a task for accepting serializable data with rules about how to grab objects from the database - if there was only one value then it was taken as a plain value and passed along to the template context. This lead to having tasks that required a lot of knowledge of how this stuff worked so I decided a level of abstraction was appropriate for keeping the code more concise and readable.
Full Code
```
# -- coding: utf-8 --
from __future__ import unicode_literals
import logging
from django.core.mail import EmailMultiAlternatives as EmailMulti
from django.template.loader import render_to_string
from project.celery import app
logger = logging.getLogger(__name__)
class TemplateEmailTask(app.Task):
'''
Abstract base task for sending an email with one or more templates.
Implementors should define
1. get_context_data, a method like Django Views that should provide a
dictionary for passing to the template renderer,
2. a name class attribute to uniquely identify this task to Celery, and
3. a list of template names to use in rendering the email.
An example implementation would be:
class MyEmail(TemplateEmailTask):
name = 'python.module.path.to.tasks.my_email'
template_names = []
def get_context_data(self, **kwargs):
return {
today: timezone.now()
}
It is totally allowed to make database calls in get_context_data (that's
why it was built this way) to provide data to the template.
Note: it is not
Solution
Few things here:
First
PEP257
Dosstrings should use tripple-double-quotes but not tripple-single-quotes as you do.
Second
To avoid some issues, you better use immutable defaults so to_emails should be either
Third
This compression can be a bit prettified
First
PEP257
Dosstrings should use tripple-double-quotes but not tripple-single-quotes as you do.
Second
def make_message(subject=None,
text_body=None,
from_email='',
to_emails=[],
html_content=None,
reply_to=''):To avoid some issues, you better use immutable defaults so to_emails should be either
tuple or None.Third
rendered = {suffix: rendered
for suffix, rendered in
[self.render(template_name, context)
for template_name in self.get_template_names()]}This compression can be a bit prettified
render = partial(self.render, context=context)
rendered = dict(map(render, self.get_template_names()))Code Snippets
def make_message(subject=None,
text_body=None,
from_email='',
to_emails=[],
html_content=None,
reply_to=''):rendered = {suffix: rendered
for suffix, rendered in
[self.render(template_name, context)
for template_name in self.get_template_names()]}render = partial(self.render, context=context)
rendered = dict(map(render, self.get_template_names()))Context
StackExchange Code Review Q#146528, answer score: 2
Revisions (0)
No revisions yet.