I'm working on my Django SAAS app in which I want to allow the user to have some custom settings, like disable or enable certain filters. For that I'm using django-user-setttings combined with django-filters and simple forms with boolean fields:
class PropertyFilterSetting(forms.Form):
filter_by_loans = forms.BooleanField(required=False)
filter_by_tenants = forms.BooleanField(required=False)
The issue is that when trying to apply those settings, I keep running into serious spaghetti code:
views.py
class PropertyListView(LoginRequiredMixin, FilterView):
template_name = 'app/property_list.html'
context_object_name = 'properties'
def get_filterset_class(self):
print(get_user_setting('filter_by_tenants', request=self.request))
return PropertyFilterWithoutTenant if not get_user_setting('filter_by_tenants', request=self.request)['value'] else PropertyFilter
filter.py
class PropertyFilter(django_filter.FilterSet):
...
class PropertyFilterWithoutTenant(PropertyFilter):
...
and I'd have to do the same thing with the rest of the features. Is there any better way to implement this?
You can create methods in your User model, or a new class which acts as a store for all the methods. Each method will give you the relevant filterset class based on the value of corresponding user setting.
Something like:
class UserFilterset:
def __init__(self, request):
self.request = request
def get_property_filterset(self):
if not get_user_setting('filter_by_tenants', request=self.request)['value']:
return PropertyFilterWithoutTenant
return PropertyFilter
... # add more such methods for each user setting
Now you can use this method to get the relevant filterset class
class PropertyListView(LoginRequiredMixin, FilterView):
template_name = 'app/property_list.html'
context_object_name = 'properties'
def get_filterset_class(self):
return UserFilterset(self.request).get_property_filterset()
So even if in future you want to add some more logic, you can just update the relevant method, it would be cleaner and manageable.
I'm not sure how MVT stucture will exactly respond to this one but i use a custom generic class in REST structure to add custom filter fields in any viewset that i want
class ListAPIViewWithFilter(ListAPIView):
def get_kwargs_for_filtering(self):
filtering_kwargs = {}
if self.my_filter_fields is not []:
# iterate over the filter fields
for field in self.my_filter_fields:
# get the value of a field from request query parameter
field_value = self.request.query_params.get(field)
if field_value:
filtering_kwargs[field] = field_value
return filtering_kwargs
def get_queryset(self):
queryset = super(ListAPIViewWithFilter, self).get_queryset()
filtering_kwargs = self.get_kwargs_for_filtering()
if filtering_kwargs != {}:
# filter the queryset based on 'filtering_kwargs'
queryset = queryset.filter(**filtering_kwargs)
self.pagination_class = None
else:
return queryset
return queryset[:self.filter_results_number_limit]
changing origional get_queryset function in views.py should be the key to solving your problem (it works in django rest).
try checking what function gets the data then just identify the field wanted from it.
I'm in a situation where I want to change the serializer field depending on a condition. Where the condition comes doesn't matter but I want to be able to switch between serializer fields as the following example:
class EntrySerializer(serializers.ModelSerializer):
# author defined from ModelSerializer
def get_author_serializer(self):
request = self.context.get('request')
GET = getattr(request, 'GET', {})
if request and GET and GET.get('include_author')=='true':
author_serializer = UserSerializer()
else:
author_serializer = serializers.PrimaryKeyRelatedField(
read_only=True, default=serializers.CurrentUserDefault()
)
return author_serialize
Of course this doesn't work because get_FIELD_serializer doesn't exist, but I'm looking for the simplest solution that can do this.
I've tried writing author as a property in a naive attempt but it didn't work.
I am aware that I can write multiple EntrySerializers and use get_serializer_class but that is just too much boilerplate code for such a small customization.
If you just want to change a serializers' field based on a condition, you could do something like this.
class MySerializer(serializers.ModelSerializer):
author = serializers.SerializerMethodField()
def get_author(self, instance):
# your condition here
if your_condition:
return instance.author
return 'hello'
Check the docs for SerializerMethodField
https://www.django-rest-framework.org/api-guide/fields/#serializermethodfield
I have written a function that accept as input a string and do some validation tasks on it and also changes the value.
def validate(str):
# do validation. If any error, raise Validation error
# modify value of str
return str
I want to use this function as a validator for some django model field. I know how to do it. My problem is that in addition to validation I want the modified value, i.e. return value of function, to be saved in field.
The models.py module is not right place to do this as input validation is usually done in forms. But still you can do it in Model.save() method:
# models.py
def validate(str):
# do validation. If any error, raise Validation error
# modify value of str
return str
class YourModel(models.Model):
...
field_to_validate = models.CharField(max_length=100)
...
def save(self, **kwargs):
try:
self.field_to_validate = validate(self.field_to_validate)
except YourValidationError:
self.field_to_validate = ''
super(YourModel, self).save(**kwargs)
I am working in Django and I have a situation where I have written a custom validator that lives in the model.py
This validator should return a validationError when the input is bad.
In the project I am working on, we are using Django Rest Framework for our API and the Django admin panel for our admin panel. They connect to the same DB
My problem is that when the request comes from the API I need to return a 'serializers.ValidationError' (which contains a status code of 400), but when the request comes from the admin panel I want to return a 'django.core.exceptions.ValidationError' which works on the admin panel. The exceptions.ValidationError does not display correctly in the API and the serializers.ValidationError causes the admin panel to break. Is there some way I can send the appropriate ValidationError to the appropriate place?
here is my validation function (it lives in the model)
def validate_unique(self, *args, **kwargs):
super(OrganizationBase, self).validate_unique(*args, **kwargs)
qs = self.__class__._default_manager.filter(organization_type="MEMBER")
if not self._state.adding and self.pk is not None:
qs = qs.exclude(pk=self.pk)
if qs.exists():
raise serializers.ValidationError("Only one organization with \'Organization Type\' of \'Member\' is allowed.") #api
raise exceptions.ValidationError("Only one organization with \'Organization Type\' of \'Member\' is allowed.") #admin
Those two lines at the end are the two errors written together for illustration's sake, in this case only the #api one would run
Basically I want to send errorA when the request is coming from the admin panel and errorB when the request is coming from the API
Thank you
For raising different error classes write different validators.
rest framework api:
You can use the UniqueValidator or a custom validation function. check link [1]
eg:
class MySerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = (....)
def validate(self, data):
# my validation code
raise serializers.ValidationError(....)
return data
admin panel:
for the admin panel you can use a custom form [2].
eg:
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def clean(self):
cleaned_data = super(MyForm, self).clean()
# my validation code
raise exceptions.ValidationError(....)
return cleaned_data
class MyAdmin(admin.ModelAdmin):
form = MyForm
In both the serializer and form you can access the instance object if not none.
[1] http://www.django-rest-framework.org/api-guide/validators/#uniquevalidator
[2] https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.ModelAdmin.form
I'm trying to implement partial_update with Django Rest Framework but I need some clarification because I'm stuck.
Why do we need to specify partial=True?
In my understanding, we could easily update Demo object inside of partial_update method. What is the purpose of this?
What is inside of serialized variable?
What is inside of serialized variable in partial_update method? Is that a Demo object? What function is called behind the scenes?
How would one finish the implementation here?
Viewset
class DemoViewSet(viewsets.ModelViewSet):
serializer_class = DemoSerializer
def partial_update(self, request, pk=None):
serialized = DemoSerializer(request.user, data=request.data, partial=True)
return Response(status=status.HTTP_202_ACCEPTED)
Serializer
class DemoSerializer(serializers.ModelSerializer):
class Meta:
model = Demo
fields = '__all__'
def update(self, instance, validated_data):
print 'this - here'
demo = Demo.objects.get(pk=instance.id)
Demo.objects.filter(pk=instance.id)\
.update(**validated_data)
return demo
I when digging into the source code of rest_framework and got the following findings:
For question 1. Why do we need to specify partial=True?
This question is related to HTTP verbs.
PUT: The PUT method replaces all current representations of the target resource with the request payload.
PATCH: The PATCH method is used to apply partial modifications to a resource.
Generally speaking, partial is used to check whether the fields in the model is needed to do field validation when client submitting data to the view.
For example, we have a Book model like this, pls note both of the name and author_name fields are mandatory (not null & not blank).
class Book(models.Model):
name = models.CharField('name of the book', max_length=100)
author_name = models.CharField('the name of the author', max_length=50)
# Create a new instance for testing
Book.objects.create(name='Python in a nut shell', author_name='Alex Martelli')
For some scenarios, we may only need to update part of the fields in the model, e.g., we only need to update name field in the Book. So for this case, client will only submit the name field with new value to the view. The data submit from the client may look like this:
{"pk": 1, name: "PYTHON IN A NUT SHELL"}
But you may have notice that our model definition does not allow author_name to be blank. So we have to use partial_update instead of update. So the rest framework will not perform field validation check for the fields which is missing in the request data.
For testing purpose, you can create two views for both update and partial_update, and you will get more understanding what I just said.
Example:
views.py
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import UpdateModelMixin
from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
class BookUpdateView(GenericAPIView, UpdateModelMixin):
'''
Book update API, need to submit both `name` and `author_name` fields
At the same time, or django will prevent to do update for field missing
'''
queryset = Book.objects.all()
serializer_class = BookSerializer
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
class BookPartialUpdateView(GenericAPIView, UpdateModelMixin):
'''
You just need to provide the field which is to be modified.
'''
queryset = Book.objects.all()
serializer_class = BookSerializer
def put(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs)
urls.py
urlpatterns = patterns('',
url(r'^book/update/(?P<pk>\d+)/$', BookUpdateView.as_view(), name='book_update'),
url(r'^book/update-partial/(?P<pk>\d+)/$', BookPartialUpdateView.as_view(), name='book_partial_update'),
)
Data to submit
{"pk": 1, name: "PYTHON IN A NUT SHELL"}
When you submit the above json to the /book/update/1/, you will got the following error with HTTP_STATUS_CODE=400:
{
"author_name": [
"This field is required."
]
}
But when you submit the above json to /book/update-partial/1/, you will got HTTP_STATUS_CODE=200 with following response,
{
"id": 1,
"name": "PYTHON IN A NUT SHELL",
"author_name": "Alex Martelli"
}
For question 2. What is inside of serialized variable?
serialized is a object wrapping the model instance as a serialisable object. and you can use this serialized to generate a plain JSON string with serialized.data .
For question 3. How would one finish the implementation here?
I think you can answer yourself when you have read the answer above, and you should have known when to use update and when to used partial_update.
If you still have any question, feel free to ask. I just read part of the source code of the rest framework, and may have not understand very deeply for some terms, and please point it out when it is wrong...
For partial update - PATCH http method
For full update - PUT http method
When doing an update with DRF, you are supposed to send request data that includes values for all (required) fields. This is at least the case when the request is via the PUT http method. From what I understand, you want to update one or at least not all model instance fields. In this case make a request with the PATCH http method. Django rest framework (DRF) will take care of it out of the box.
Example (with token auth):
curl -i -X PATCH -d '{"name":"my favorite banana"}' -H "Content-Type: application/json" -H 'Authorization: Token <some token>' http://localhost:8000/bananas/
So simple, just override init method of your serializer like that:
def __init__(self, *args, **kwargs):
kwargs['partial'] = True
super(DemoSerializer, self).__init__(*args, **kwargs)
Just a quick note as it seems that nobody has already pointed this out:
serialized = DemoSerializer(request.user, data=request.data, partial=True)
The first argument of DemoSerializer should be a Demo instance, not a user (at least if you use DRF 3.6.2 like me).
I don't know what you are trying to do, but this is a working example:
def partial_update(self, request, *args, **kwargs):
response_with_updated_instance = super(DemoViewSet, self).partial_update(request, *args, **kwargs)
Demo.objects.my_func(request.user, self.get_object())
return response_with_updated_instance
I do the partial update and then I do other things calling my_func and passing the current user and the demo instance already updated.
Hope this helps.
I had an issue where my multi-attribute/field validation in a rest_framework serializer was working with a POST /resources/ request but failing with a PATCH /resources/ request. It failed in the PATCH case because it was only looking for values in the supplied attrs dict and not falling back to values in self.instance. Adding a method get_attr_or_default to do that fallback seems to have worked:
class EmailSerializer(serializers.ModelSerializer):
def get_attr_or_default(self, attr, attrs, default=''):
"""Return the value of key ``attr`` in the dict ``attrs``; if that is
not present, return the value of the attribute ``attr`` in
``self.instance``; otherwise return ``default``.
"""
return attrs.get(attr, getattr(self.instance, attr, ''))
def validate(self, attrs):
"""Ensure that either a) there is a body or b) there is a valid template
reference and template context.
"""
existing_body = self.get_attr_or_default('body', attrs).strip()
if existing_body:
return attrs
template = self.get_attr_or_default('template', attrs)
templatecontext = self.get_attr_or_default('templatecontext', attrs)
if template and templatecontext:
try:
render_template(template.data, templatecontext)
return attrs
except TemplateRendererException as err:
raise serializers.ValidationError(str(err))
raise serializers.ValidationError(NO_BODY_OR_TEMPLATE_ERROR_MSG)
I don't know why, but for me, the only way to solve it was to override the validate method in the Serializer class.
Maybe it's related to the fact that I'm using MongoDB with Djongo
class DemoSerializer(serializers.ModelSerializer):
def validate(self, attrs):
self._kwargs["partial"] = True
return super().validate(attrs)
You forgot serializer.save()
You can finish it the following way . . .
class DemoViewSet(viewsets.ModelViewSet):
serializer_class = DemoSerializer
def partial_update(self, request, pk=None):
serializer = DemoSerializer(request.user, data=request.data, partial=True)
serializer.save()
serializer.is_valid(raise_exception=True)
return Response(serializer.data)
Also, you shouldn't need to override the update method in the serializer.