Async in Django ListCreateApiView - django

I have a simply ListCreateApiView class in Django, for example:
class Test(ListCreateApiView):
def list(self, request, *args, **kwargs):
""" Some code which is creating excel file with openpyxl and send as response """
return excel_file
The problem is, when user send a request to get an excel_file, he must wait for 5-10 minutes till the file will return. User can't go to others urls untill file will not download (I have 10k+ objects in my db)
So, how can I add async here? I want that while the file is being formed, a person can follow other links of the application.
Thanks a lot!

Django has some troubles with asynchrony, and async calls won't help you in cpu-intensive tasks, you'd better use some distributed task queue like celery
It will allow you to handle such heavy tasks (i.e. excel generation) in another thread without interrupting the main django thread. Follow the guide for integration with django.
Once installed and configured, create a task and fire it when a user reaches the endpoint:
E.g.:
# tasks.py
from celery import shared_task
#shared_task
def generate_excel(*args, **kwargs):
...
# views.py
from tasks import generate_excel
class Test(ListCreateApiView):
def list(self, request, *args, **kwargs):
generate_excel.delay()
return Response()

Related

Is there a way to start/stop a celery task from django views?

I just finished my celery task for my django app, but I'm looking a way now for how to start/stop it through a toggle button on my frontend UI. I know it can do on django-admin(built in). but I want to make it for the app.
like from a typical django view and restapi, you create a function like this:
def start_task and def stop_task and call it through api(URL) to execute it.
How can I do it? Thanks!
Sure, a task is just a function you need to call to start and if you've got a task_id then you can revoke a task. More on that explain in this answer
I've got an example of starting a task which updates a search index.
#app.task
def update_search():
""" Update haystack """
try:
update_index.Command().handle(
remove=True, interactive=False, verbosity=2
)
except TransportError as e:
logger.error(e)
This is called from a view which is accessed using an action link in the admin;
class UpdateSearchView(View):
"""
View called from the admin that updates search indexes.
Checks the requesting user is a super-user before proceeding.
"""
admin_index = reverse_lazy('admin:index')
#staticmethod
def _update_search(request):
"""Update search index"""
update_search.delay()
messages.success(
request, _('Search index is now scheduled to be updated')
)
def get(self, request, *args, **kwargs):
"""Update search, re-direct back the the referring URL"""
if request.user.is_anonymous() or not request.user.is_superuser:
raise PermissionDenied
self._update_search(request)
# redirect back to the current page (of index if there is no
# current page)
return HttpResponseRedirect(request.GET.get('next', self.admin_index))

How to set upload_handlers on a per-request basis using Django Rest Framework

I have a DRF view where I need to ensure that uploaded files land on the filesystem and not just in memory. DRF respects Django's FILE_UPLOAD_HANDLERS setting, but I don't want to change it for my whole app, just this one view.
I know that in a regular Django view I could set request.upload_handlers to my desired value, but that doesn't seem to work in DRF. I've tried doing it from .initialize_request() in my viewset, like so:
def initialize_request(self, request, *args, **kwargs):
request.upload_handlers = ["django.core.files.uploadhandler.TemporaryFileUploadHandler"]
return super().initialize_request(request, *args, **kwargs)
but I'm getting:
AttributeError: You cannot set the upload handlers after the upload has been processed.
What is the correct way for me to set the upload handlers for a single DRF view (in particular, the create action of a generic viewset)?
It seems like you are not assigning the upload handler in the right way
from django.core.files.uploadhandler import TemporaryFileUploadHandler
from rest_framework import viewsets
class MyUploadViewSet(viewsets.ModelViewSet):
# your view class attributes goes here....
def initialize_request(self, request, *args, **kwargs):
request.upload_handlers = [TemporaryFileUploadHandler(request)] # initialization goes here
return super().initialize_request(request, *args, **kwargs)
Note
This will work as-is in all DRF class based views

Can you trigger a signal upon partial update of django object via the django rest framework?

I started noticing that the patch method in django rest framework doesn't actually trigger signals, post methods seem to work fine. This is what I have:
#receiver(signals.pre_save, sender=Example)
def populate_time_fields_based_on_state(sender, **kwargs):
example = kwargs.get('instance')
if example.start_datetime is None and example.projected_end_datetime is None and example.state.label == 'Assigned':
example.start_datetime = datetime.datetime.now()
example.projected_end_datetime = example.created_datetime + datetime.timedelta(
days=example.som_field)
example.save()
And I'm testing this via:
client = APIClient()
client.patch(f'/api/example/1/', {'state': 'Assigned'})
Is there a way to tell it to trigger the signal? Do I need to override the update method in my serializer? I'm trying this:
def partial_update(self, request, *args, **kwargs):
response = super().partial_update(request, *args, **kwargs)
instance = self.get_object()
instance.save()
return response
But it's quite hacky
In your app directory there should be an apps.py, see the docs for the format of it.
Generally it ends up looking like the following, as opposed to the example where they wire up the signal manually. Note that I have a "project/apps/" structure here, but just change the module name depending on where the files actually live:
#project/apps/my_app/__init__.py
default_app_config = 'project.apps.my_app.apps.MyAppConfig'
#project/apps/my_app/apps.py
from django.apps import AppConfig
class MyAppConfig(AppConfig):
name = "project.apps.my_app"
verbose_name = "MyApp"
def ready(self):
from project.apps.my_app import signals
# ... other init &/or logging
Note: Feel free to delete the line in init.py, and play with the name in the app config. I'm not sure how critical they actually are

Catch django signal sent from celery task

Catch django signal sent from celery task. Is it possible? As far as I know they are running in different processes
#celery.task
def my_task():
...
custom_signal.send()
#receiver(custom_signal)
def my_signal_handler():
...
Please take in mind that your async task must be #shared_task decorator. in order to be called from outside since it will not be attached to a concrete app instance. Documentation #shared_task celery
task.py
#shared_task
def send_email(email):
# Do logic for sending email or other task
signal.py
as you can see below this will only execute when post_save (After user executes save) for the model Contract in your case would be anyother model that its being executed.
#receiver(post_save, sender=Contract)
def inflation_signal(sender, **kwargs):
if kwargs['created']:
send_email.delay('mymail#example.com')

Django REST API: run operations after put request

I am trying to find a clean way to process some task after successfully completes the PUT request for REST API. I am using post_update() function but its never being called. Here is my code
class portfolio_crud(generics.RetrieveUpdateDestroyAPIView):
lookup_field = 'id'
serializer_class = user_ticker_portfolio_serializer
def get_queryset(self):
return user_ticker_portfolio.objects.filter(user = self.request.user)
def put(self, request, *args, **kwargs):
print("got the put request to update portfolio")
return self.update(request, *args, **kwargs)
def post_update(self, serializer):
print("got the post save call") #never executed
Depends on what you want to do, but I usually use django's post_save hook as opposed to something on the viewset or serializer. Something like this:
from django.db.models.signals import post_save
#receiver(post_save, sender=YourPortolioClass)
def portfolio_post_save(sender, created, instance, raw, **kwargs):
""" We need to do something after updating a portfolio
"""
if created or raw:
return
# do your update stuff here.
Not sure if this will be much help to you (but I hope so) - I just use multithreading and create another thread before returning.
I don't see anything calling that function... but I am curious