Using destroy() with some parameter other than pk - python

TLDR --> Is it possible to destroy() an item in DB using some parameter instead of pk?
I am using ViewSet in my DRF application. I am interested in destroy()-ing some queryset items. I would like to avoid using pk, can I do that? My approximate model/approach is as following.
class MyAwesomeMode(models.Model):
awesome_field = models.CharField(max_length = 256)
# some other fields
Now, my intention is to destroy() the Queryset element without using pk and using awesome_field. What do I need to do with my ViewSet?

Since you use ViewSet and that class extends from ViewSetMixin and views.APIView, the .destroy() method is not available.
You need extend from DestroyModelMixin to use the .destroy() method.
About your question, first take a look to the .destroy() source code:
def destroy(self, request, *args, **kwargs):
instance = self.get_object() # Here is getting the object!
self.perform_destroy(instance)
return Response(status=status.HTTP_204_NO_CONTENT)
So, let's say you give a unique_name param in your request, you could override the .get_object() method:
class MyView(ViewSet, DestroyModelMixin):
def get_object(self):
return MyModel.objects.get(unique_name=request.data.get('unique_name'))
Then, when .destroy() calls self.get_object() will use your method, where you got the object based on the imaginary unique_name field istead of pk.

Related

Is there any way to pass an ID from URL to views.py using generic views?

I need to pass id from the url slug. I am using generic views. This is my code for urls.py:
path('category/<int:pk>/details/',
CategoryDetailView.as_view(),
name='category-details'),
and I need to pass the <int:pk> value into views.py, so I can filter my queryset with this id.
My views.py code:
class CategoryDetailView(DetailView):
model = Category
def get_context_data(self, *, object_list=Expense.objects.get_queryset(), **kwargs):
queryset = object_list
return super().get_context_data(
summary_per_year_month = summary_per_year_month(queryset.filter(category_id= <int:pk> ))
)
You can access values from the URL in self.kwargs.
queryset.filter(category_id=self.kwargs['pk'])
Note that your get_context_data is the other way round than normal. Typically, you call super() and then add to the context dict. It looks like your way will work, but it will seem odd to other Django users. You could try writing it as follows:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
queryset=Expense.objects.get_queryset()
context['summary_per_year_month'] = summary_per_year_month(queryset.filter(category_id=self.kwargs['pk']))
return context
Yes, the path parameters are stored in self.kwargs, a dictionary that maps the name of the parameter to the value. So you can make use of:
class CategoryDetailView(DetailView):
model = Category
def get_context_data(self, *args, **kwargs):
summary=summary_per_year_month(
Expense.objects.filter(category_id=self.kwargs['pk'])
)
return super().get_queryset(*args, **kwargs, summary_per_year_month=summary)
You use self.kwargs.get('pk').
class CategoryDetailView(DetailView):
model = Category
def get_context_data(self, *, object_list=Expense.objects.get_queryset(), **kwargs):
queryset = object_list
return super().get_context_data(
summary_per_year_month = summary_per_year_month(queryset.filter(category_id=self.kwargs.get('pk')))
)
Something that really helped me learn Django was to add breakpoints (pdb) in my code, then run dir() on each object I came across.
For example, dir(self) will tell you what properties and methods 'self' has (ie, kwargs, model, request, etc). Then you can start experimenting around with these properties: self.kwargs, self.request, self.model, etc, see what they return.
Soon enough, you would find out that self.kwargs returns a dictionary of arguments that includes 'pk', which you can access using get(). That's how you can access 'pk'.
To me, this simple trick unlocked most of my understanding of Django and python.

How to display in django admin readonly data from other model?

Why when i call instance of model from method car_name the method return - in Django admin.
#admin.register(Invoice)
class CarProductDataAdmin(admin.ModelAdmin):
form = CarProductDataAdminForm
def car_name(self, obj):
# Call this instance
car = Customer.objects.get(product__customer_id=self.request.user.person.id)
return "car.name" # return string
readonly_fields = ('car_name', )
But when i just return string it is work.
#admin.register(Invoice)
class CarProductDataAdmin(admin.ModelAdmin):
form = CarProductDataAdminForm
def car_name(self, obj):
# Doesn`t Call this instance
# return string
return "name"
readonly_fields = ('car_name', )
From my judgement the django admin seems to fail silently when encountering an exception in a method, which is used as a field.
In your first example the problem seems to be self.request, as the CarProductDataAdmin instance does not have a request attribute. So instead of raising an exception no value is returned to the field resulting in an output "-".
If you need the request you must get it from somewhere and save it in your ModelAdmin for re-use. Maybe by overring the get_form method of the ModelAdmin
class CarProductDataAdmin(admin.ModelAdmin):
# ....
def get_form(self, request, obj=None, change=False, **kwargs):
self.request = request
return super().get_form(request, obj, change, **kwargs)
Then you should be able to use your car_name method as posted in your code.
First noticeable problem in your code is that instead of object attribute value you are returning a string:
return "car.name" # return string
should be:
return f"{car.name}"
or, depending on python version you are using:
return f"{}".format(car.name)
Next one is that calling get on model manager should cause DoesNotExists exception but in some situations it could be suppressed so I'll advice to modify the code to:
try:
car = Customer.objects.get(product__customer_id=self.request.user.person.id)
return f"{car.name}"
except Customer.DoesNotExists:
return "None"
And see if that return what you want or "None"

How do I pass the request object to the method_decorator in django class views?

I have been working on this all day.
I am trying to write custom permission for class views to check if user is in a certain group of permissions.
def rights_needed(reguest):
if request.user.groups.filter(Q(name='Admin')).exists():
pass
else:
return HttpResponseRedirect('/account/log-in/')
#method_decorator(rights_needed, name='dispatch')
class AdminView(CreateView):
model = Admin
form_class = AdminForm
def get_template_names(self):
return 'clinic/visitform_list.html'
Could help me know how I can achieve this? Or an easier way around it?
I also tried this (code inside AdminView class):
def dispatch(self, request):
if request.user.groups.filter(Q(name='Admin')).exists():
return super().dispatch(*args, **kwargs)
else:
return HttpResponseRedirect('/account/log-in/')
A decorator is a function that takes a function (a view in this case), and returns another function (a view in this case). At the moment your rights_needed looks like a regular view - it’s returning a response not a function.
Django comes with a user_passes_test method that makes it easy to create decorators like this. Since you are using class based views, it would be even easier to use the UserPassesTest mixin.
Your test function for the mixin would be:
def test_func(self):
return self.request.user.groups.filter(Q(name='Admin')).exists()

Unable to overload (override) DRF serializer's save() method (or other methods)

I'm trying to override my serializer's save() method (as per the docs) to support bulk instance creation. At the moment, I have something that looks like this (skip the code if you like, it's just for context. The real issue is I can't make any of my own serializer methods).
serializers.py
class BulkWidgetSerializer(serializers.ModelSerializer):
""" Serialize the Widget data """
#http://stackoverflow.com/questions/28200485/
some_foreign_key = serializers.CharField(source='fk_fizzbuzz.name', read_only=False)
class Meta:
model = Widget
fields = (
'some_foreign_key',
'uuid',
'foobar',
)
# Normally we would set uuid to read_only, but then it won't be available in the self.validate()
# method. We also need to take the validator off this field to remove the UNIQUE constraint, and
# perform the validation ourselves.
# See https://github.com/encode/django-rest-framework/issues/2996 and
# https://stackoverflow.com/a/36334825/3790954
extra_kwargs = {
'uuid': {'read_only': False, 'validators': []},
}
def validate(self, data):
return super(WidgetSerializer, self).validate(self.business_logic(data))
def save(self):
print("---------Calling save-----------")
more_business_logic()
instances = []
for widget in self.validated_data:
instances.append(Widget(**self.validated_data))
Widget.objects.bulk_create(instances)
return instances
viewset.py
class WidgetViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.IsAuthenticated,)
serializer_class = BulkWidgetSerializer
pagination_class = WidgetViewSetPagination
lookup_field = 'uuid'
def partial_update(self, request):
serializer = self.get_serializer(data=request.data,
many=isinstance(request.data, list),
partial=True)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
pdb.set_trace()
serializer.save()
return Response(serializer.data, status=status.HTTP_200_OK)
Then I get new Wiget instances in the database, but their properties indicate that the more_business_logic() call in save() didn't occur. However, I do get feedback indicating that business_logic in validate() call occurred.
I presume from this that I'm somehow still stuck with the super class's save()? How can I override this method?
Edit:
When I rename save() to newsave() in both files, and try to call it in the ViewSet, I get:
AttributeError: 'ListSerializer' object has no attribute 'newsave'
What's going on? Inspecting with pdb at the breakpoint shows that it is indeed a BulkWidgetSerializer. Inspecting in the shell shows newsave is definitely a method of that class:
>>>'newsave' in [func for func in dir(BulkWidgetSerializer) if callable(getattr(BulkWidgetSerializer, func))]
True
Moreover, if I create my own test method in the serializer class:
def test_method(self):
print("Successful test method")
I can't call that either!
>>> serializer.test_method()
AttributeError: 'ListSerializer' object has no attribute 'test_method'
Your BulkWidgetSerializer is wrapped by ListSerializer which is default behavior of DRF. That's why your new methods are missing.
If you instantiate any subclass of BaseSerializer with kwarg many=True the library wraps it with new ListSerializer with child set to your Serializer class.
Because of that you cannot override save() method to get desired effect.
Try overriding many_init classmethod of your serializer to provide custom ListSerializer which implements desired behavior, as shown in DRF documentation.
Secondly it's better to override create() or update() methods instead of save() which calls one of them.
Your implementation could look something like that:
class CustomListSerializer(serializers.ListSerializer):
def create(self, validated_data):
more_business_logic()
instances = [
Widget(**attrs) for attrs in validated_data
]
return Widget.objects.bulk_create(instances)
And then in BulkWidgetSerializer:
#classmethod
def many_init(cls, *args, **kwargs):
kwargs['child'] = cls()
return CustomListSerializer(*args, **kwargs)
A gotcha: don't forget to pass the right kwargs from the parent to the child, eg kwargs['child'] = cls( partial=kwargs.get('partial') ) if you rely on any of them in your child class during your overwritten methods to support bulk partial updates (eg validate()).
It seems that you are instantiating your serializer with many=True. In this case the ListSerializer is instantiated internally (you can find the code for this in the class method rest_framework.serializers.BaseSerializer.many_init).
Hence the save() method of the ListSerializer is called. If you must override the save method, first create a custom list serializer:
class CustomListSerializer(serializers.ListSerializer):
def save(self):
...
Then add this custom list serializer to your BulkWidgetSerializer by specifying list_serializer_class:
class Meta:
list_serializer_class = CustomListSerializer
As specified by others, it is better to override either the create or update methods instead of save
You have been reading the wrong part of documentation and your approach is not correct.
The default implementation for multiple object creation is to simply
call .create() for each item in the list. If you want to customize
this behavior, you'll need to customize the .create() method on
ListSerializer class that is used when many=True is passed.
This ensures your more_business_logic you want to happen will happen on each item you pass in that list.
As per the documentation - http://www.django-rest-framework.org/api-guide/serializers/#customizing-multiple-create

Dynamically limiting queryset of related field

Using Django REST Framework, I want to limit which values can be used in a related field in a creation.
For example consider this example (based on the filtering example on https://web.archive.org/web/20140515203013/http://www.django-rest-framework.org/api-guide/filtering.html, but changed to ListCreateAPIView):
class PurchaseList(generics.ListCreateAPIView)
model = Purchase
serializer_class = PurchaseSerializer
def get_queryset(self):
user = self.request.user
return Purchase.objects.filter(purchaser=user)
In this example, how do I ensure that on creation the purchaser may only be equal to self.request.user, and that this is the only value populated in the dropdown in the form in the browsable API renderer?
I ended up doing something similar to what Khamaileon suggested here. Basically I modified my serializer to peek into the request, which kind of smells wrong, but it gets the job done... Here's how it looks (examplified with the purchase-example):
class PurchaseSerializer(serializers.HyperlinkedModelSerializer):
def get_fields(self, *args, **kwargs):
fields = super(PurchaseSerializer, self).get_fields(*args, **kwargs)
fields['purchaser'].queryset = permitted_objects(self.context['view'].request.user, fields['purchaser'].queryset)
return fields
class Meta:
model = Purchase
permitted_objects is a function which takes a user and a query, and returns a filtered query which only contains objects that the user has permission to link to. This seems to work both for validation and for the browsable API dropdown fields.
Here's how I do it:
class PurchaseList(viewsets.ModelViewSet):
...
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class()
context = self.get_serializer_context()
return serializer_class(*args, request_user=self.request.user, context=context, **kwargs)
class PurchaseSerializer(serializers.ModelSerializer):
...
def __init__(self, *args, request_user=None, **kwargs):
super(PurchaseSerializer, self).__init__(*args, **kwargs)
self.fields['user'].queryset = User._default_manager.filter(pk=request_user.pk)
The example link does not seem to be available anymore, but by reading other comments, I assume that you are trying to filter the user relationship to purchases.
If i am correct, then i can say that there is now an official way to do this. Tested with django rest framework 3.10.1.
class UserPKField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
user = self.context['request'].user
queryset = User.objects.filter(...)
return queryset
class PurchaseSeriaizer(serializers.ModelSerializer):
users = UserPKField(many=True)
class Meta:
model = Purchase
fields = ('id', 'users')
This works as well with the browsable API.
Sources:
https://github.com/encode/django-rest-framework/issues/1985#issuecomment-328366412
https://medium.com/django-rest-framework/limit-related-data-choices-with-django-rest-framework-c54e96f5815e
I disliked the style of having to override the init method for every place where I need to have access to user data or the instance at runtime to limit the queryset. So I opted for this solution.
Here is the code inline.
from rest_framework import serializers
class LimitQuerySetSerializerFieldMixin:
"""
Serializer mixin with a special `get_queryset()` method that lets you pass
a callable for the queryset kwarg. This enables you to limit the queryset
based on data or context available on the serializer at runtime.
"""
def get_queryset(self):
"""
Return the queryset for a related field. If the queryset is a callable,
it will be called with one argument which is the field instance, and
should return a queryset or model manager.
"""
# noinspection PyUnresolvedReferences
queryset = self.queryset
if hasattr(queryset, '__call__'):
queryset = queryset(self)
if isinstance(queryset, (QuerySet, Manager)):
# Ensure queryset is re-evaluated whenever used.
# Note that actually a `Manager` class may also be used as the
# queryset argument. This occurs on ModelSerializer fields,
# as it allows us to generate a more expressive 'repr' output
# for the field.
# Eg: 'MyRelationship(queryset=ExampleModel.objects.all())'
queryset = queryset.all()
return queryset
class DynamicQuersetPrimaryKeyRelatedField(LimitQuerySetSerializerFieldMixin, serializers.PrimaryKeyRelatedField):
"""Evaluates callable queryset at runtime."""
pass
class MyModelSerializer(serializers.ModelSerializer):
"""
MyModel serializer with a primary key related field to 'MyRelatedModel'.
"""
def get_my_limited_queryset(self):
root = self.root
if root.instance is None:
return MyRelatedModel.objects.none()
return root.instance.related_set.all()
my_related_model = DynamicQuersetPrimaryKeyRelatedField(queryset=get_my_limited_queryset)
class Meta:
model = MyModel
The only drawback with this is that you would need to explicitly set the related serializer field instead of using the automatic field discovery provided by ModelSerializer. i would however expect something like this to be in rest_framework by default.
In django rest framework 3.0 the get_fields method was removed. But in a similar way you can do this in the init function of the serializer:
class PurchaseSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Purchase
def __init__(self, *args, **kwargs):
super(PurchaseSerializer, self).__init__(*args, **kwargs)
if 'request' in self.context:
self.fields['purchaser'].queryset = permitted_objects(self.context['view'].request.user, fields['purchaser'].queryset)
I added the if check since if you use PurchaseSerializer as field in another serializer on get methods, the request will not be passed to the context.
First to make sure you only allow "self.request.user" when you have an incoming http POST/PUT (this assumes the property on your serializer and model is named "user" literally)
def validate_user(self, attrs, source):
posted_user = attrs.get(source, None)
if posted_user:
raise serializers.ValidationError("invalid post data")
else:
user = self.context['request']._request.user
if not user:
raise serializers.ValidationError("invalid post data")
attrs[source] = user
return attrs
By adding the above to your model serializer you ensure that ONLY the request.user is inserted into your database.
2) -about your filter above (filter purchaser=user) I would actually recommend using a custom global filter (to ensure this is filtered globally). I do something for a software as a service app of my own and it helps to ensure each http request is filtered down (including an http 404 when someone tries to lookup a "object" they don't have access to see in the first place)
I recently patched this in the master branch so both list and singular views will filter this
https://github.com/tomchristie/django-rest-framework/commit/1a8f07def8094a1e34a656d83fc7bdba0efff184
3) - about the api renderer - are you having your customers use this directly? if not I would say avoid it. If you need this it might be possible to add a custom serlializer that would help to limit the input on the front-end
Upon request # gabn88, as you may know by now, with DRF 3.0 and above, there is no easy solution.
Even IF you do manage to figure out a solution, it won't be pretty and will most likely fail on subsequent versions of DRF as it will override a bunch of DRF source which will have changed by then.
I forget the exact implementation I used, but the idea is to create 2 fields on the serializer, one your normal serializer field (lets say PrimaryKeyRelatedField etc...), and another field a serializer method field, which the results will be swapped under certain cases (such as based on the request, the request user, or whatever). This would be done on the serializers constructor (ie: init)
Your serializer method field will return a custom query that you want.
You will pop and/or swap these fields results, so that the results of your serializer method field will be assigned to the normal/default serializer field (PrimaryKeyRelatedField etc...) accordingly. That way you always deal with that one key (your default field) while the other key remains transparent within your application.
Along with this info, all you really need is to modify this: http://www.django-rest-framework.org/api-guide/serializers/#dynamically-modifying-fields
I wrote a custom CustomQueryHyperlinkedRelatedField class to generalize this behavior:
class CustomQueryHyperlinkedRelatedField(serializers.HyperlinkedRelatedField):
def __init__(self, view_name=None, **kwargs):
self.custom_query = kwargs.pop('custom_query', None)
super(CustomQueryHyperlinkedRelatedField, self).__init__(view_name, **kwargs)
def get_queryset(self):
if self.custom_query and callable(self.custom_query):
qry = self.custom_query()(self)
else:
qry = super(CustomQueryHyperlinkedRelatedField, self).get_queryset()
return qry
#property
def choices(self):
qry = self.get_queryset()
return OrderedDict([
(
six.text_type(self.to_representation(item)),
six.text_type(item)
)
for item in qry
])
Usage:
class MySerializer(serializers.HyperlinkedModelSerializer):
....
somefield = CustomQueryHyperlinkedRelatedField(view_name='someview-detail',
queryset=SomeModel.objects.none(),
custom_query=lambda: MySerializer.some_custom_query)
#staticmethod
def some_custom_query(field):
return SomeModel.objects.filter(somefield=field.context['request'].user.email)
...
I did the following:
class MyModelSerializer(serializers.ModelSerializer):
myForeignKeyFieldName = MyForeignModel.objects.all()
def get_fields(self, *args, **kwargs):
fields = super(MyModelSerializer, self).get_fields()
qs = MyModel.objects.filter(room=self.instance.id)
fields['myForeignKeyFieldName'].queryset = qs
return fields
I looked for a solution where I can set the queryset upon creation of the field and don't have to add a separate field class. This is what I came up with:
class PurchaseSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Purchase
fields = ["purchaser"]
def get_purchaser_queryset(self):
user = self.context["request"].user
return Purchase.objects.filter(purchaser=user)
def get_extra_kwargs(self):
kwargs = super().get_extra_kwargs()
kwargs["purchaser"] = {"queryset": self.get_purchaser_queryset()}
return kwargs
The main issue for tracking suggestions regarding this seems to be drf#1985.
Here's a re-usable generic serializer field that can be used instead of defining a custom field for every use case.
class DynamicPrimaryKeyRelatedField(serializers.PrimaryKeyRelatedField):
"""A PrimaryKeyRelatedField with ability to set queryset at runtime.
Pass a function in the `queryset_fn` kwarg. It will be passed the serializer `context`.
The function should return a queryset.
"""
def __init__(self, queryset_fn=None, **kwargs):
assert queryset_fn is not None, "The `queryset_fn` argument is required."
self.queryset_fn = queryset_fn
super().__init__(**kwargs)
def get_queryset(self):
return self.queryset_fn(context=self.context)
Usage:
class MySerializer(serializers.ModelSerializer):
my_models = DynamicPrimaryKeyRelatedField(
queryset_fn=lambda context: MyModel.objects.visible_to_user(context["request"].user)
)
# ...
Same works for serializers.SlugRelatedField.

Categories