14

Hello Awesome People!

Before my question, I tried these SO posts:

None of them works!

I want to keep users on a website update with new courses. With a queryset of Courses, I want to send them via email.

send_daemon_email.delay(instance=instance,all_courses=Course.objects.all())

And my function looks like:

@shared_task
def send_daemon_email(instance,all_courses):
    ctx = {'instance':instance,'all_courses':all_courses}
    message = get_template("emails/ads.html").render(ctx)
    ''' '''

When I tried to send the email to a specific user The error I got is

<User: First Name> is not JSON serializable

Just because delay() from celery got a non serialized data.

How I can send Django objects to celery task so I can use it in the template? I know that I can send information needed as python object

send_daemon_email.delay(first_name='Name',
      last_name='Lapr',all_courses = [{'title1':'title1',},{'title2':'title2',}])

but it would be too much info.

Any hint will be appreciated. Thank you!

2
  • 1
    Well you can not pass objects itself, since those are not serializable, but you can for example pass primary keys, etc. Commented Jun 3, 2018 at 13:51
  • with the pk, I can retrieve the object within the function? that's a good point Commented Jun 3, 2018 at 13:53

2 Answers 2

10

Django objects can't be sent in celery tasks, you can serialize with django serializers (from django.core import serializers) by providing fields needed in template and the lookup will work like a django object in template

NOTE: with serializer you will need to dump and load the data

or just convert your queryset to a list like the following:

send_daemon_email.delay(
    instance = User.objects.filter(pk=user.pk).values('first_name','last_name'),
    all_courses= list(Course.objects.values('title','other_field'))
)

All you need is to provide fields that you really need in template with values('')

@shared_task
def send_daemon_email(instance,all_courses):
    ctx = {
        'instance': instance,
        'all_courses': all_courses,
    }
    message = get_template("emails/ads.html").render(ctx)

In templates {% for course in all_courses %}{{course}}{% endfor %} will display all the courses, and {{ instance.first_name }} the user

Sign up to request clarification or add additional context in comments.

2 Comments

That's an interesting answer! let me give it a try
Yeah! it works, values() is better than doing the query twice, the answer of @WillemVanOnsem is a good one, it's more elaborated.
10

Well typically tools like celery, use a format to pass messages. Here JSON is used, and not every Python object can, by default, be turned into a JSON object.

We can however for example pass the primary keys, and then turn these in objects again at the receiver side. So for example:

send_daemon_email.delay(
    instance=instance.pk,
    all_courses=list(Course.objects.all().values_list('pk', flat=True))
)

and then at the side of the receiver, we can fetch the objects with:

@shared_task
def send_daemon_email(instance,all_courses):
    ctx = {
        'instance': User.objects.get(pk=instance),
        'all_courses': Course.objects.filter(pk__in=all_courses)
    }
    message = get_template("emails/ads.html").render(ctx)

Of course we do not per se need to pass primary keys: any kind of object that can be JSON serialized (or by manually serializing) can be used. Although I would not make it too complicated, usually simple things work better than more complex (which is one of the credos of Python).

8 Comments

Thank you! we do the query twice. serialize data may be better
@EuChi: well in case you only need the direct attributes, you can use model_to_dict. But serializing has a cost as well: since Python will have to dump and restore the data in a JSON stream.
There is a problem with this approach is that if the object has been altered before a worker picking a task, then the worker will be working on the altered object and not the same object the task was instantiated with.
@Coderji: well that works in both ways: if you pass the values, then you might process an outdated object, and thus the invoice your for example generate, might contain the wrong data. Usually the idea should be that you add enough triggers that a new task is scheduled if the object changes.
@Coderji: passing values also erodes the model "logic", and thus often makes it harder to write the handler effectively: django-antipatterns.com/antipattern/over-use-of-values.html
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.