Django REST API: run operations after put request - django

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

Related

Django rest framework- calling another class-based view

I have pored over several similar posts (and Calling a class-based view of an app from another app in same project seemed promising, but does not work), but some are older and none quite work for me. Here's my setup (using Django==2.0.6, djangorestframework==3.8.2)
I have a basic model (simplified here):
from django.db import models
class Resource(models.Model):
name = models.CharField(max_length=100, null=False)
I have a basic endpoint where I can list and create Resource instances:
from rest_framework import generics, permissions
from myapp.models import Resource
from myapp.serializers import ResourceSerializer
class ListAndCreateResource(generics.ListCreateAPIView):
queryset = Resource.objects.all()
serializer_class = ResourceSerializer
permission_classes = (permissions.IsAuthenticated,)
(afaik, the details of the serializer are not relevant, so that is left out).
Anyway, in addition to that basic endpoint, I have another API endpoint which performs some actions, but also creates some Resource objects in the process. Of course, I would like to make use of the functionality encapsulated in the ListAndCreateResource class so I only have to maintain one place where Resources are created.
I have tried:
Attempt 1:
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource().post(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
Unfortunately, that does not work for me. In my trace, I get:
File "/home/projects/venv/lib/python3.5/site-packages/rest_framework/generics.py", line 111, in get_serializer
kwargs['context'] = self.get_serializer_context()
File "/home/projects/venv/lib/python3.5/site-packages/rest_framework/generics.py", line 137, in get_serializer_context
'request': self.request,
AttributeError: 'ListAndCreateResource' object has no attribute 'request'
Attempt 2:
This attempt tries to use the as_view method which is part of all Django class-based views:
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource.as_view()(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
But that gives up with:
AssertionError: The `request` argument must be an instance of `django.http.HttpRequest`, not `rest_framework.request.Request`
So my question is...is there a straightforward way to do this? I can access the _request attribute of the rest_framework.request.Request object (which is of type django.http.HttpRequest, but then I do not have any of the authentication details that are contained in the DRF Request object (indeed, my ListAndCreateResource returns a 403 if I use response = ListAndCreateResource().as_view()(request._request, *args, **kwargs) in attempt #2 above).
Thanks in advance!
This seems a bit late, but in case anyone is wondering.
class SomeOtherView(generics.CreateAPIView):
def post(self, request, *args, **kwargs):
# ... some other functionality...
# ...
response = ListAndCreateResource.as_view()(request, *args, **kwargs)
# ... more functionality...
return Response({'message': 'ok'})
The as_view() is a function that when called, returns a function that takes a request, *args, **kwargs. So basically, a class view is an encapsulated function view.
I think you can use request._request. The DRF keeps a protected member _request, as is, received from the API call.
You can access the request with self.request in class based views.

Execute delete() within save() in Django

I'm working on a Django/Wagtail project. I'm trying to build a very customized feature that requires an object to be deleted when hitting the Save button when certain conditions are met.
I override the Save method:
def save(self, *args, **kwargs):
if condition:
return super(ArticleTag, self).delete()
else:
return super(ArticleTag, self).save(*args, **kwargs)
I know this looks very odd and completely anti-adviseable, but it is exactly the behavior I'm trying to achieve.
Is there a better or "correct" way to do this?
Are there other steps to exactly reproduce the behavior as if the user had hit Delete directly?
If the object already exists in your db, you can do as follows:
def save(self, *args, **kwargs):
if condition:
self.delete() # you do not need neither to return the deleted object nor to call the super method.
else:
return super(ArticleTag, self).save(*args, **kwargs)
Using signals receivers
signals.py
from django.dispatch import receiver
from django.db.models.signals import post_save
__all__ = ['check_delete_condition']
#receiver(post_save, sender="yourapp.yourmodel")
def check_delete_condition(instance, raw, created, using, updatefields, **kwargs):
if condition:
instance.delete()
in your apps.py you can't put the signals import
from .signals import *
#rest of code

post_delete/pre_delete signals not firing for specific sender

I´ve got a model "Comment" and a signal to take actions when a comment is deleted.
The signal executes when deleting the comment in the admin, but not when deleted through django-rest-framework.
#receiver(post_delete, sender=Comment, dispatch_uid=str(uuid.uuid1())) # I tried also removing dispatch_uid
def comment_post_delete(sender, instance, *args, **kwargs):
I´m not really sure if this is related to django-rest-framework but that´s how my app works.
Other thing to note is that many other signals are working just fine.
All the signals are declared in a separate file signals.py and I import it at the end of models.py with a simple import signals
The only difference with other delete operations is that I´m overriding the "destroy" method of the viewset:
class CommentViewSet(mixins.CreateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
viewsets.GenericViewSet):
serializer_class = CommentSerializer
def destroy(self, request, *args, **kwargs):
# only the comment author or the media owner are allowed to delete
instance = self.get_object()
if request.user != instance.user and request.user != instance.media.owner:
error = {'detail': 'No tienes permiso para borrar este comentario'}
return Response(data=error, status=status.HTTP_403_FORBIDDEN)
return super(CommentViewSet, self).destroy(request, args, kwargs)
post_delete and pre_delete will not be fired if the sender parameter does not match the model you are expecting.
To check the sender, create a receiver without sender parameter:
#receiver(post_delete)
def comment_post_delete(sender, instance, *args, **kwargs):
if sender == Comment:
# do something
Why could a signal get dispatched with a different model if the model being saved was "Comment"?
This can happen when django automatically set a deferred model, so when I was expecting a "Comment" I was getting something like "Comment_deferred_somefield".
Django automatic deferring can happen for example when the query is using Model.objects.only('field1', 'field2', 'etc') and there are some missing fields in the only() method

Get current user log in signal in Django

I am just using the admin site in Django. I have 2 Django signals (pre_save and post_save). I would like to have the username of the current user. How would I do that? It does not seem I can send a request Or I did not understand it.
Thanks
Being reluctant to mess around with thread-local state, I decided to try a different approach. As far as I can tell, the post_save and pre_save signal handlers are called synchronously in the thread that calls save(). If we are in the normal request handling loop, then we can just walk up the stack to find the request object as a local variable somewhere. e.g.
from django.db.models.signals import pre_save
from django.dispatch import receiver
#receiver(pre_save)
def my_callback(sender, **kwargs):
import inspect
for frame_record in inspect.stack():
if frame_record[3]=='get_response':
request = frame_record[0].f_locals['request']
break
else:
request = None
...
If there's a current request, you can grab the user attribute from it.
Note: like it says in the inspect module docs,
This function relies on Python stack frame support in the interpreter, which isn’t
guaranteed to exist in all implementations of Python.
If you are using the admin site why not use a custom model admin
class MyModelAdmin( admin.ModelAdmin ):
def save_model( self, request, obj, form, change ):
#pre save stuff here
obj.save()
#post save stuff here
admin.site.register( MyModel, MyModelAdmin )
A signal is something that is fired every time the object is saved regardless of if it is being done by the admin or some process that isn't tied to a request and isn't really an appropriate place to be doing request based actions
You can use a middleware to store the current user: http://djangosnippets.org/snippets/2179/
Then you would be able to get the user with get_current_user()
We can solve this problem using middleware classes.
Create singleton class in where will be storing user variable.
class Singleton(type):
'''
Singleton pattern requires for GetUser class
'''
def __init__(cls, name, bases, dicts):
cls.instance = None
def __call__(cls, *args, **kwargs):
if cls.instance is None:
cls.instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls.instance
class NotLoggedInUserException(Exception):
'''
'''
def __init__(self, val='No users have been logged in'):
self.val = val
super(NotLoggedInUser, self).__init__()
def __str__(self):
return self.val
class LoggedInUser(object):
__metaclass__ = Singleton
user = None
def set_user(self, request):
if request.user.is_authenticated():
self.user = request.user
#property
def current_user(self):
'''
Return current user or raise Exception
'''
if self.user is None:
raise NotLoggedInUserException()
return self.user
#property
def have_user(self):
return not user is None
Create own middleware class that will be setting user for LoggedInUser instance,and insert out middleware after 'django.contrib.auth.middleware.AuthenticationMiddleware' in settings.py
from useranytimeaccess import LoggedInUser
class LoggedInUserMiddleware(object):
'''
Insert this middleware after django.contrib.auth.middleware.AuthenticationMiddleware
'''
def process_request(self, request):
'''
Returned None for continue request
'''
logged_in_user = LoggedInUser()
logged_in_user.set_user(request)
return None
In signals import LoggedInUser class and get current user
logged_in = LoggedInUser()
user = logged_in.user
In both signals, signal send three arguments,
Sender
Instance
Using
What you need is the Instant being modified...
def signal_catcher(sender, instance, **kwargs):
uname = instance.username
http://docs.djangoproject.com/en/dev/ref/signals/#pre-save

Django model: delete() not triggered

I have a model:
class MyModel(models.Model):
...
def save(self):
print "saving"
...
def delete(self):
print "deleting"
...
The save()-Method is triggered, but the delete() is not. I use the latest svn-Version (Django version 1.2 pre-alpha SVN-11593), and concerning the documentation at http://www.djangoproject.com/documentation/models/save_delete_hooks/ this should work.
Any ideas?
I think you're probably using the admin's bulk delete feature, and are running into the fact that the admin's bulk delete method doesn't call delete() (see the related ticket).
I've got round this in the past by writing a custom admin action for deleting models.
If you're not using the admin's bulk delete method (e.g. you're clicking the delete button on the object's edit page) then something else is going on.
See the warning here:
The “delete selected objects” action
uses QuerySet.delete() for efficiency
reasons, which has an important
caveat: your model’s delete() method
will not be called.
If you wish to override this behavior,
simply write a custom action which
accomplishes deletion in your
preferred manner – for example, by
calling Model.delete() for each of the
selected items.
For more background on bulk deletion,
see the documentation on object
deletion.
My custom admin model looks like this:
from photoblog.models import PhotoBlogEntry
from django.contrib import admin
class PhotoBlogEntryAdmin(admin.ModelAdmin):
actions=['really_delete_selected']
def get_actions(self, request):
actions = super(PhotoBlogEntryAdmin, self).get_actions(request)
del actions['delete_selected']
return actions
def really_delete_selected(self, request, queryset):
for obj in queryset:
obj.delete()
if queryset.count() == 1:
message_bit = "1 photoblog entry was"
else:
message_bit = "%s photoblog entries were" % queryset.count()
self.message_user(request, "%s successfully deleted." % message_bit)
really_delete_selected.short_description = "Delete selected entries"
admin.site.register(PhotoBlogEntry, PhotoBlogEntryAdmin)
I know this question is ancient, but I just ran into this again and wanted to add that you can always move your code to a pre_delete or post_delete signal like so:
from django.db.models.signals import pre_delete
from django.dispatch.dispatcher import receiver
#receiver(pre_delete, sender=MyModel)
def _mymodel_delete(sender, instance, **kwargs):
print("deleting")
It works with the admin's bulk delete action (at least as of 1.3.1).
The bulk action of the admin calls queryset.delete().
You could override the .delete() method of the queryset,
so it always does a 1-by-1 deletion of objects. For example:
in managers.py:
from django.db import models
from django.db.models.query import QuerySet
class PhotoQuerySet(QuerySet):
""" Methods that appear both in the manager and queryset. """
def delete(self):
# Use individual queries to the attachment is removed.
for photo in self.all():
photo.delete()
In models.py:
from django.db import models
class Photo(models.Model):
image = models.ImageField(upload_to='images')
objects = PhotoQuerySet.as_manager()
def delete(self, *args, **kwargs):
# Note this is a simple example. it only handles delete(),
# and not replacing images in .save()
super(Photo, self).delete(*args, **kwargs)
self.image.delete()
Using django v2.2.2, I solved this problem with the following code
models.py
class MyModel(models.Model):
file = models.FileField(upload_to=<path>)
def save(self, *args, **kwargs):
if self.pk is not None:
old_file = MyModel.objects.get(pk=self.pk).file
if old_file.path != self.file.path:
self.file.storage.delete(old_file.path)
return super(MyModel, self).save(*args, **kwargs)
def delete(self, *args, **kwargs):
ret = super(MyModel, self).delete(*args, **kwargs)
self.file.storage.delete(self.file.path)
return ret
admin.py
class MyModelAdmin(admin.ModelAdmin):
def delete_queryset(self, request, queryset):
for obj in queryset:
obj.delete()
For the DefaultAdminSite the delete_queryset is called if the user has the correct permissions, the only difference is that the original function calls queryset.delete() which doesn't trigger the model delete method. This is less efficient since is not a bulk operation anymore, but it keeps the filesystem clean =)
The main issue is that the Django admin's bulk delete uses SQL, not instance.delete(), as noted elsewhere. For an admin-only solution, the following solution preserves the Django admin's "do you really want to delete these" interstitial. vdboor's solution is the most general, however.
from django.contrib.admin.actions import delete_selected
class BulkDeleteMixin(object):
class SafeDeleteQuerysetWrapper(object):
def __init__(self, wrapped_queryset):
self.wrapped_queryset = wrapped_queryset
def _safe_delete(self):
for obj in self.wrapped_queryset:
obj.delete()
def __getattr__(self, attr):
if attr == 'delete':
return self._safe_delete
else:
return getattr(self.wrapped_queryset, attr)
def __iter__(self):
for obj in self.wrapped_queryset:
yield obj
def __getitem__(self, index):
return self.wrapped_queryset[index]
def __len__(self):
return len(self.wrapped_queryset)
def get_actions(self, request):
actions = super(BulkDeleteMixin, self).get_actions(request)
actions['delete_selected'] = (BulkDeleteMixin.action_safe_bulk_delete, 'delete_selected', ugettext_lazy("Delete selected %(verbose_name_plural)s"))
return actions
def action_safe_bulk_delete(self, request, queryset):
wrapped_queryset = BulkDeleteMixin.SafeDeleteQuerysetWrapper(queryset)
return delete_selected(self, request, wrapped_queryset)
class SomeAdmin(BulkDeleteMixin, ModelAdmin):
...