0

Normal functions can be executed as django admin actions. I want to export data as csv file. Due to the size of data, I am trying to execute this as a celery task. But objects of model, request, queryset etc cannot be passed to a task. Is there any way to execute a admin action as celery task.

shinoymm
  • 315
  • 1
  • 3
  • 10
  • Of course you can pass a model instance, request object or even a queryset to a Celery task - it's just a function. – Brandon Taylor May 13 '15 at 14:06
  • @Brandon Is that possible ? Celery user guide states it is not. http://docs.celeryproject.org/en/2.2/userguide/tasks.html#state As per them : "Another gotcha is Django model objects. They shouldn’t be passed on as arguments to tasks. It’s almost always better to re-fetch the object from the database when the task is running instead, as using old data may lead to race conditions." – shinoymm May 14 '15 at 11:24
  • Whether or not it's a bad idea mostly depends on the use case, but yes, it's possible. – Brandon Taylor May 14 '15 at 12:13
  • @Brandon When I simply passes a model object to a task, say test_task and call test_task.delay(my_object), it gives an Exception: EncodeError : Can't pickle : attribute lookup __builtin__.function failed – shinoymm May 14 '15 at 12:20
  • I'm away from my computer, but you should add your model code to help determine what's up with the pickle error. You can always just pass the id of the object and get it back in the task. – Brandon Taylor May 14 '15 at 12:29

1 Answers1

1

To execute an admin action from a celery task or from anywhere (e.g. a management command):

from celery import shared_task
from django.contrib import admin
from django.test.client import RequestFactory
from django.contrib.auth.models import User

@shared_task
def my_task(pk_of_model):
    '''
    Task executes a delete_selected admin action.
    '''

    # the queryset is the set of objects selected from the change list
    queryset = MyModel.objects.filter(pk=pk_of_model)

    # we use the django request factory to create a bogus request
    rf = RequestFactory()

    # the post data must reflect as if a user selected the action
    # below we use a 'delete' action and specify post:'post' to
    # simulate the user confirmed the delete

    request = rf.post(
        '/admin/app/model',   # url of the admin change list
        {
            '_selected_action': [m.pk for m in queryset],
            'action': 'delete_selected',
            'post': 'post', 
        }
    )

    # the request factory does not use any middlewares so we add our
    # system user - some admin user all the tasks and commands run as.
    request.user = User.objects.get(username='SYSTEM') # must exist

    # the admin site registry holds all the ModelAdmin
    # instances where our actions are declared
    admin.site._registry[MyModel].delete_selected(request, queryset)

The example above will fail because the delete_selected action relies on the messages middleware and the request factory does not use any. One could wrap the final execution line in a try: ... except MessageFailure: pass but most likely you will be executing your own custom action where you can check if the message middleware is enabled.