Export Excel file using celery

I have this code:

@method_decorator(
    login_required(login_url='authentication:login', redirect_field_name='next'),
    name='dispatch'
)
class ExportFilms(View):
    def get(self, request):
        response = HttpResponse(content_type='application/ms-excel')
        response['Content-Disposition'] = 'attachment; filename="relatorio.xls"'

        films = Film.objects.filter(user=request.user)
        export_films_excel.delay(response, films)
        return response

and this task

@shared_task
def export_films_excel(response, films):
    wb = xlwt.Workbook(encoding='utf-8')
    ws = wb.add_sheet('Filmes')

    row_num = 0
    columns = ['Id', 'Nome', 'Descrição', 'Gostaria de Assistir']

    for col_num in range(len(columns)):
        ws.write(row_num, col_num, columns[col_num])

    row_num = 1
    for film in films:
        ws.write(row_num, 0, film.id)
        ws.write(row_num, 1, film.name)
        ws.write(row_num, 2, film.description)
        ws.write(row_num, 3,
            'Gostaria' if film.would_like is True else 'Assistido')
        row_num += 1
    wb.save(response)

but dont work

image

and i dont know what i do for export excel with celery the other form

Hey there, Carlos!
I think that you’re probably misundestarding the concept of having celery for this type of task. The usecase is correct, is just being applied incorrectly…
Celery is a way for you to run python code (a task) outside the request/response flow of Django, so delaying the task (meaning that you will be awaiting until the task is finished) is not much different than just calling that function directly from your view. So you never going to pass to celery a request/response object, because they can’t be serialized (that’s why you’re getting the error) and the request/response won’t have any meaning in the worker context.
So normally in this scenario, you would:

  • Record this export request on your Database (in a ExportRequest model for example);
  • Put the task on the queue with apply_async passing the ID of the export request;
  • Returning the ID of the export request to the frontend.

In some other moment the worker will:

  • Receive the export request id;
  • Process the file and save it on the ExportRequest table;

After that, the frontend can go to another endpoint to get the status of the given ExportRequest id, and after the worker finishes the user can then download the file.

This would be the correct workflow for a “async” export.
If you want a sync export, you wouldn’t use celery.

Hope this helps clarify your thoughts.

Hello Leandro! Thanks for help. you have one example or link where i can based?

I will try my best with the example you given.

Assume you have this model:

# models.py
from django.db import models
from django.utils.translation import gettext_lazy as _

class FilmExportRequest(models.Model):
    user = models.ForeignKey(
        to="users.User", # or wherever is your user model
        on_delete=models.CASCADE,
        related_name="film_export_requests",
        verbose_name=_("User"),
        help_text=_("The user that requested the export"),
    )
    report = models.FileField(
        verbose_name=_("Report"),
        upload_to="films/exports",
        help_text=_("The report file will be populated after a few moments"),
        null=True,
        blank=True,
    )
    created_at = models.DateTimeField(
        verbose_name=_("Created at"),
        help_text=_("When the report has been created"),
        null=True,
        blank=True,
        auto_now_add=True,
    )
    started_at = models.DateTimeField(
        verbose_name=_("Started at"),
        help_text=_("When the report has started to be processed"),
        null=True,
        blank=True,
    )
    finished_at = models.DateTimeField(
        verbose_name=_("Finished at"),
        help_text=_("When the report has finished"),
        null=True,
        blank=True,
    )

    class Meta:
        verbose_name = _("Film Export Request")
        verbose_name_plural = _("Film Export Requests")

Then you would have this task.

from django.utils import timezone

@shared_task
def export_films_excel(film_export_request_id):
    film_export_request = FilmExportRequest.objects.get(pk=film_export_request_id)
    film_export_request.started_at = timezone.now()
    film_export_request.save()

    # Your code as-is
    wb = xlwt.Workbook(encoding='utf-8')
    ws = wb.add_sheet('Filmes')

    row_num = 0
    columns = ['Id', 'Nome', 'Descrição', 'Gostaria de Assistir']

    for col_num in range(len(columns)):
        ws.write(row_num, col_num, columns[col_num])

    row_num = 1
    for film in Film.objects.filter(user=film_export_request.user):
        ws.write(row_num, 0, film.id)
        ws.write(row_num, 1, film.name)
        ws.write(row_num, 2, film.description)
        ws.write(row_num, 3,
            'Gostaria' if film.would_like is True else 'Assistido')
        row_num += 1

    # You would need to figure it out this write on your own
    # wb.save(response)

    film_export_request.finished_at = timezone.now()
    film_export_request.save()

Then on your view:

@method_decorator(
    login_required(login_url='authentication:login', redirect_field_name='next'),
    name='dispatch'
)
class ExportFilms(View):
    def get(self, request):

        export_request = FilmExportRequest(user=request.user)
        export_request.save()
        export_films_excel.apply_async(kwargs={"film_export_request_id": export_request.id})
        return render(request, "some_template", context={"export_request": export_request})

The example is not functional as-is but will give you a pretty good start.

1 Like

Wow! Leandro you save me. Thankss!!! This was much useful.