I have a multiple objects of a File model.
I am trying filter and delete these files based on a certain condition but am failing to succeed in this.
Consider the following
I have 3 File objects:
File1
File2
File3
I tried to override the delete() function of the model like this:
def delete(self, using=None, keep_parents=False):
test_qs = File.objects.filter(file_name='File1')
if test_qs:
for x in test_qs:
x.delete()
super(File, self).delete()
When I go to my Django Admin, select all the files (File1, File2 & File3) and bulk delete them, all of them are deleted instead of just File1.
In my Django Console File.objects.filter(file_name='File1') returns a queryset with just File1.
I also tried to override the pre_delete signal like this:
#receiver(pre_delete, sender=File)
def delete_certain_files(sender, instance, **kwargs):
test_qs = File.objects.filter(file_name='File1')
test_qs.delete()
This however, results in a RecursionError
How do I make sure to just delete the File objects that meet a certain condition upon bulk deletion?
So, If you want this on admin. Imagine we have Foo model and FooAdmin class
class FooAdmin(admin.ModelAdmin):
actions = ['delete_selected']
def delete_selected(self, request, queryset):
# request: WSGIRrequest
# queryset: QuerySet, this is used for deletion
lookup_kwargs = {'pk__gt': 5000} # you can add your own condition.
queryset.filter(**lookup_kwargs)
admin.site.register(Foo, FooAdmin)
Related
I need to notify users by email, when MyModel object is created. I need to let them know all attributes of this object including ManyToManyFields.
class MyModel(models.Model):
charfield = CharField(...)
manytomany = ManyToManyField('AnotherModel'....)
def to_email(self):
return self.charfield + '\n' + ','.join(self.manytomany.all())
def notify_users(self):
send_mail_to_all_users(message=self.to_email())
The first thing I tried was to override save function:
def save(self, **kwargs):
created = not bool(self.pk)
super(Dopyt, self).save(**kwargs)
if created:
self.notify_users()
Which doesn't work (manytomany appears to be empty QuerySet) probably because transaction haven't been commited yet.
So I tried post_save signal with same result - empty QuerySet.
I can't use m2mchanged signal because:
manytomany can be None
I need to notify users only if object was created, not when it's modified
Do you know how to solve this? Is there some elegant way?
I want to create a viewset/apiview with a path like this: list/<slug:entry>/ that once I provide the entry it will check if that entry exists in the database.
*Note: on list/ I have a path to a ViewSet. I wonder if I could change the id with the specific field that I want to check, so I could see if the entry exists or not, but I want to keep the id as it is, so
I tried:
class CheckCouponAPIView(APIView):
def get(self, request, format=None):
try:
Coupon.objects.get(coupon=self.kwargs.get('coupon'))
except Coupon.DoesNotExist:
return Response(data={'message': False})
else:
return Response(data={'message': True})
But I got an error: get() got an unexpected keyword argument 'coupon'.
Here's the path: path('check/<slug:coupon>/', CheckCouponAPIView.as_view()),
Is there any good practice that I could apply in my situation?
What about trying something like this,
class CheckCouponAPIView(viewsets.ModelViewSet):
# other fields
lookup_field = 'slug'
From the official DRF Doc,
lookup_field - The model field that should be used to for performing
object lookup of individual model instances. Defaults to pk
I made a little endpoint, adapting DRF ReadOnlyModelViewSet, defined as follows:
class MyApi(viewsets.ReadOnlyModelViewSet):
queryset = []
serializer_class = MySerializer
def get_queryset(self):
print 'Debug: I am starting...\n\n\n\n'
# do a lot of things filtering data from Django models by some information on neo4j and saving data in the queryset...
return self.queryset
When I call MyApi via URL, it returns results without any problems, but sometimes it returns doubled result!! It's very strange...It's not a systematic error but happens only sometimes.
I use the line print 'Debug: I am starting...\n\n\n\n' in Apache log to investigate the problem. When that doubling happens, I read in the log:
Debug: I am starting...
Debug: I am starting...
It seems like get_queryset is called more than one time. It's very strange. I didn't report the detail of the logic inside that method, I think the problem is elsewhere or that is a bug...How can I solve?
You have defined queryset as a class attribute.
class MyApi(viewsets.ReadOnlyModelViewSet):
queryset = []
That means that each time you append to self.queryset, you are appending to the same list. Your get_queryset method is only called once, but self.queryset already has entries in it at the beginning of the method. To see the problem in action, print self.queryset in your method at the very beginning, before you change it.
You would be better to do something like:
class MyApi(viewsets.ReadOnlyModelViewSet):
queryset = None # this line is probably not required, but some code checking tools like it to be defined.
def get_queryset(self):
self.queryset = []
...
return self.queryset
If you are using a custom permission like DjangoModelPermissions. You need to check that the get_queryset method of your View is not being called.
For example, DjangoModelPermissions call this method here:
if hasattr(view, 'get_queryset'):
queryset = view.get_queryset()
else:
queryset = getattr(view, 'queryset', None)
If I use this permission just as it is. The method get_queryset will be called twice.
So I changed it to just this:
queryset = getattr(view, 'queryset', None)
As a result it is just called once. Hope this helps.
You are defining queryset = [] as class attributes and class attributes are "shared by all instances". So, if you python process append some data to your queryset attribute, the subsequent view instances would have that data, only if they are created by the same process.
Had the same problem and just ran a trackback
Turns out that the second run is the rest_framework's rendering files calling view.get_queryset().
Try calling it from the command line and then checking the results.
I'm reading about customizing multiple update here and I haven't figured out in what case the custom ListSerializer update method is called. I would like to update multiple objects at once, I'm not worried about multiple create or delete at the moment.
From the example in the docs:
# serializers.py
class BookListSerializer(serializers.ListSerializer):
def update(self, instance, validated_data):
# custom update logic
...
class BookSerializer(serializers.Serializer):
...
class Meta:
list_serializer_class = BookListSerializer
And my ViewSet
# api.py
class BookViewSet(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
And my url setup using DefaultRouter
# urls.py
router = routers.DefaultRouter()
router.register(r'Book', BookViewSet)
urlpatterns = patterns('',
url(r'^api/', include(router.urls)),
...
So I have this set up using the DefaultRouter so that /api/Book/ will use the BookSerializer.
Is the general idea that if I POST/PUT/PATCH an array of JSON objects to /api/Book/ then the serializer should switch over to BookListSerializer?
I've tried POST/PUT/PATCH JSON data list to this /api/Book/ that looks like:
[ {id:1,title:thing1}, {id:2, title:thing2} ]
but it seems to still treat the data using BookSerializer instead of BookListSerializer. If I submit via POST I get Invalid data. Expected a dictionary, but got list. and if I submit via PATCH or PUT then I get a Method 'PATCH' not allowed error.
Question:
Do I have to adjust the allowed_methods of the DefaultRouter or the BookViewSet to allow POST/PATCH/PUT of lists? Are the generic views not set up to work with the ListSerializer?
I know I could write my own list deserializer for this, but I'm trying to stay up to date with the new features in DRF 3 and it looks like this should work but I'm just missing some convention or some option.
Django REST framework by default assumes that you are not dealing with bulk data creation, updates, or deletion. This is because 99% of people are not dealing with bulk data creation, and DRF leaves the other 1% to third-party libraries.
In Django REST framework 2.x and 3.x, a third party package exists for this.
Now, you are trying to do bulk creation but you are getting an error back that says
Invalid data. Expected a dictionary, but got list
This is because you are sending in a list of objects to create, instead of just sending in one. You can get around this a few ways, but the easiest is to just override get_serializer on your view to add the many=True flag to the serializer when it is a list.
def get_serializer(self, *args, **kwargs):
if "data" in kwargs:
data = kwargs["data"]
if isinstance(data, list):
kwargs["many"] = True
return super(MyViewSet, self).get_serializer(*args, **kwargs)
This will allow Django REST framework to know to automatically use the ListSerializer when creating objects in bulk. Now, for other operations such as updating and deleting, you are going to need to override the default routes. I'm going to assume that you are using the routes provided by Django REST framework bulk, but you are free to use whatever method names you want.
You are going to need to add methods for bulk PUT and PATCH to the view as well.
from rest_framework.response import Response
def bulk_update(self, request, *args, **kwargs):
partial = kwargs.pop("partial", False)
queryset = self.filter_queryset(self.get_queryset))
serializer = self.get_serializer(instance=queryset, data=request.data, many=True)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
def partial_bulk_update(self, *args, **kwargs):
kargs["partial"] = True
return super(MyView, self).bulk_update(*args, **kwargs)
This won't work out of the box as Django REST framework doesn't support bulk updates by default. This means you also have to implement your own bulk updates. The current code will handle bulk updates as though you are trying to update the entire list, which is how the old bulk updating package previously worked.
While you didn't ask for bulk deletion, that wouldn't be particularly difficult to do.
def bulk_delete(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
self.perform_delete(queryset)
return Response(status=204)
This has the same effect of removing all objects, the same as the old bulk plugin.
None of this code was tested. If it doesn't work, consider it as a detailed example.
In an admin.py I have setup an action called export as you can see below...
class RecipientAdmin(admin.ModelAdmin):
actions = [export]
export.short_description = "Export Stuff"
admin.site.register(Recipient, RecipientAdmin)
This runs the following function...
def export(modeladmin, request, queryset):
return HttpResponseRedirect("/export/")
My question is...
How can I pass the queryset to another view/page it is possible using HttpResponseRedirect? or is there another way I should try to do this?
I want the records that have been preselected on the list view to be carried to the new page so I can iterate over them.
There are two ways to do this.
1> If all you want do is filter by some fields in the model then you can pass the filters in url. Example: '/export/?id_gte=3&status_exact=3'
2> In your export action function you can set some variable or entire queryset in session and then check for it in you export view
def export(modeladmin, request, queryset):
"""
not sure if this will work
"""
request.session['export_querset'] = queryset
"""
if above does not work then just set this and check for it in view and u can make the queryset again if this is set
"""
request.session['export_querset'] = 1
return HttpResponseRedirect("/export/")