Django rest API Framework : Serializer use dynamically - python

I am trying to create a framework using Django rest API.
I want to configure Serializer fields and Name of class variable of serializer dynamically using a factory pattern. My code is a bit complex so here is the simplified version of problem.
from rest_framework import serializers
class FixedSrializer(serializers.Serializer):
email= serializers.EmailField()
fixed_serializer=FixedSrializer(data={"email":"invalidemail"})
fixed_serializer.errors
#OUTPUT: {'email': [u'Enter a valid email address.']}
# Trying Above by dynamically assigning class var
class CustomSrializer(serializers.Serializer):
pass # no class var assigned
#setattr(CustomSrializer,"email",serializers.EmailField())
CustomSrializer.email=serializers.EmailField()
custom_serializer=CustomSrializer(data={"email":"invalidemail"})
custom_serializer.errors
#OUTPUT# {}
#EXPECTED# {'email': [u'Enter a valid email address.']}
I think this may be because of dynamically changing class variable. Please correct where I am doing wrong.
If you see my post a bit carefully 1) FixedSrializer => contains email fields 2) CustomSrializer. Does not contain email field but in very next line assigned. Rest every thing is same for both and I am getting different outputs.I am using django-rest-framework.org.

Related

Django REST Framework - Reference to the User who added the order

I am creating an application in Django REST Fremework, in which the user can add an order.
I would like the serializer to set a reference to the user based on the token and complete the "Client" model field.
It's actually works with HiddenField, as shown in the documentation.
(Link: https://www.django-rest-framework.org/api-guide/fields/#hiddenfield)
class OrderSerializer(serializers.ModelSerializer):
client = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Order
fields = '__all__'
The problem is that when I fetch a single order or list of orders, Client field is of course hidden becouse of HiddenField type.
curl -X GET http://127.0.0.1:8000/api/orders/12
{
"id":12,
"name":"sprzatanie ogrodka",
"description":"dupa",
"price":"12.20",
"work_time_hours":2,
"work_time_minutes":50,
"workers_needed_num":3,
"coords_latitude":"-1.300000",
"coords_longitude":"1.100000",
"created_at":"2020-03-08T13:20:16.455289Z",
"finished_at":null,
"category":1,
"workers":[]
}
I would like the field to still capture reference to the logged in user, but at the same time to be visible when returning data from the API.
What serializers field type I need to use?
Thanks!
Going through the documentation i found: https://www.django-rest-framework.org/api-guide/validators/
Using a standard field with read_only=True, but that also includes a default=… argument. This field will be used in the serializer output representation, but cannot be set directly by the user.
this is what you need i think.
So whatever field type you have set in Model can be used with read_only=True
For example:
client = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())
Hope this helps

Nested validation in Django Rest Framework

Using django rest framework I want to validate fields.
Correct input request:
{
test_field_a: {test_field_c: 25},
test_field_b: {}
}
My serializers.py (I don't have any associated models and the models.py itself):
from rest_framework import serializers
class TestSerializer(serializers.Serializer):
test_field_a = serializers.JSONField(label='test_field_a', allow_null=False, required=True)
test_field_b = serializers.JSONField(label='test_field_b', required=True)
test_field_c = serializers.IntegerField(label='test_field_c)
Wrong input request (which should state that int field is required) :
{
test_field_a: {test_field_c: 'wrong'},
test_field_b: {}
}
Now test_field_a and test_field_b are validated as required. But how to make validation of fields on different levels of the request? (in this case test_field_c)
JSONField just checks that a field contains correct JSON structure. You need to do it plus check values from this JSON.
There are several ways to do it:
You can write your own custom field type (it's nice if you are planning to do something similar in other serializers);
You can change field validation (try something like this):
from rest_framework import serializers
class TestSerializer(serializers.Serializer)::
test_field_a = serializers.JSONField(label='test_field_a', allow_null=False, required=True)
test_field_b = serializers.JSONField(label='test_field_b', required=True)
def validate_test_field_a(self, value):
"""
Check that test_field_a .
"""
if not isinstance(value.get('test_field_c'), int):
raise serializers.ValidationError("Some error message")
return value
You can try nested validation:
from rest_framework import serializers
class Test1Serializer(serializers.Serializer):
test_field_c = serializers.IntegerField(label='test_field_c')
class TestSerializer(serializers.Serializer):
test_field_a = Test1Serializer()
test_field_b = serializers.JSONField(label='test_field_b', required=True)
The serializer's JSONField does not have a validation for nested fields because it is not meant to nest explicitly declared fields and as far as I know, there is currently no way to specify a json schema to validate it.
What you can do is validate the field yourself by declaring a validate_test_field_a validation method.
For example:
def validate_test_field_a(self, value):
if 'test_field_c' not in value:
raise serializers.ValidationError('`test_field_c` is required')
return value
Generally, if you find yourself needing to validate the nested type inside the JSONField, then it is a sign of bad architecture and you should consider using nested serializers instead. Same applies to using JSONField in the model

Custom permissions on Django group form

I've added some custom permissions to my Post model.
I've also created a form to add/edit groups with only this custom permissions:
class GroupFornm(forms.ModelForm):
permissions = forms.MultipleChoiceField(choices=Post._meta.permissions)
class Meta:
model = Group
fields = '__all__'
It works because I can see and select only my custom permissions but when I try to save the form I got the error:
invalid literal for int() with base 10: 'can_view'
What am I doing wrong? It seems that this form field waits for (int, str) pair but documentation says that as usually, (str, str) should work.
Edit
Post._meta.permissions:
(('can_view', 'Can see tickets'), ('can_edit', 'Can edit tickets'), ('can_delete', 'Can delete tickets'), ('can_create', 'Can add new tickets'))
The problem is not really related to the form itself, but the fact that you somehow need to translate those permissions into Permission objects that should be stored in the Group instance (the one that this ModelForm is managing).
I think displaying the options is not a problem. But if a user later for example performs a POST request, with the options (like can_write), then the question is how the Form should translate these into Permission objects (or the primary keys of Permission objects).
In that case you need to coerce the name of the permissions to Permission objects, or the ids of Permission objects. We can for example use a TypedMultipleChoiceField, and coerce with:
def get_permission_from_name(name):
return Permission.objects.get(name=name)
class GroupFornm(forms.ModelForm):
permissions = forms.TypedMultipleChoiceField(
choices=Post._meta.permissions,
coerce=get_permission_from_name,
)
class Meta:
model = Group
fields = '__all__'
Note that the above is not really a very efficient implementation, since it requires a query for every value send. Furthermore in case no permission with that name exists, then this will raise an error.
If you want to construct Permissions on the fly (in case these are not yet constructed), then you can change the function to:
def get_permission_from_name(name):
return Permission.objects.get_or_create(
name=name,
defaults={
'content_type': ContentType.objects.get_for_model(Post),
'codename': name
}
)

HTTP requests for nested objects

Is it possible to use the library 'requests' (HTTP library for python) to post and update nested objects in django rest framework?
I made a new create method in serializers, but I can't post outside the shell, nor with the requests library or in the api webview.
My Serializers:
class QualityParameterSerializer(serializers.ModelSerializer):
class Meta:
model = QualityParameter
fields = ("id","name", "value")
class ProductQualityMonitorSerializer(serializers.ModelSerializer):
parameters = QualityParameterSerializer(many=True)
class Meta:
model = ProductQualityMonitor
fields = ("id","product_name", "area", "timeslot", "processing_line",
"updated_on",'parameters')
def create(self, validated_data):
params_data = validated_data.pop('parameters')
product = ProductQualityMonitor.objects.create(**validated_data)
for param_data in params_data:
QualityParameter.objects.create(product=product, **param_data)
return product
POST HTTP request
If I may suggest the following form for your serializer:
from django.db import transaction
class ProductQualityMonitorSerializer(serializers.ModelSerializer):
parameters = QualityParameterSerializer(many=True)
class Meta:
model = ProductQualityMonitor
fields = (
"id",
"updated_on",
"product_name",
"area",
"timeslot",
"processing_line",
"parameters",
)
def create(self, validated_data):
# we will use transactions, so that if one of the Paramater objects isn't valid
# that we will rollback even the parent ProductQualityMonitor object creation,
# leaving no dangling objects in the database
params_data = validated_data.pop('parameters')
with transaction.atomic():
product = ProductQualityMonitor.objects.create(**validated_data)
# you can create the objects in a batch, hitting the dB only once
params = [QualityParameter(product=product, **param) for param in params_data]
QualityParameter.objects.bulk_create(params)
return product
About using python requests library: you will have to pay attention to the following aspects when posting to a django back-end:
you must provide a valid CSRF token in your request; the way this is done is via csrf-token cookie;
you must provide the proper authentication headers / tokens / cookies; this is your choice, depends how you're implementing this on the DRF back-end
if this is a request from one domain to another domain, then you have to care for CORS setup.
More to the point: what have you tried already and didn't worked ?

How to post to a Django REST Framework API with Related Models

I have two related models (Events + Locations) with a serialzer shown below:
class Locations
title = models.CharField(max_length=250)
address = model.CharField(max_length=250)
class Events
title = models.CharField(max_length=250)
locations = models.ForeignKey(Locations, related_name='events'
class EventsSerializer(serializers.ModelSerializer):
class Meta:
model = Events
depth = 1
I set the depth to 1 in the serializer so I can get the information from the Locations model instead of a single id. When doing this however, I cant post to events with the location info. I can only perform a post with the title attribute. If I remove the depth option in the serializer, I can perform the post with both the title and location id.
I tried to create a second serializer (EventsSerialzerB) without the depth field with the intention of using the first one as a read-only response, however when I created a second serializer, viewset, and added it to the router, it would automatically override the original viewset.
Is it possible for me to create a serializer that outputs the related model fields, and allows you to post directly to the single model?
// EDIT - Here's what I'm trying to post
$scope.doClick = function (event) {
var test_data = {
title: 'Event Test',
content: 'Some test content here',
location: 2,
date: '2014-12-16T11:00:00Z'
}
// $resource.save() doesn't work?
$http.post('/api/events/', test_data).
success(function(data, status, headers, config) {
console.log('sucess', status);
}).
error(function(data, status, headers, config) {
console.log('error', status);
});
}
So when the serializers are flat, I can post all of these fields. The location field is the id of a location from the related Locations table. When they are nested, I can't include the location field in the test data.
By setting the depth option on the serializer, you are telling it to make any relation nested instead of flat. For the most part, nested serializers should be considered read-only by default, as they are buggy in Django REST Framework 2.4 and there are better ways to handle them in 3.0.
It sounds like you want a nested representation when reading, but a flat representation when writing. While this isn't recommended, as it means GET requests don't match PUT requests, it is possible to do this in a way to makes everyone happy.
In Django REST Framework 3.0, you can try the following to get what you want:
class LocationsSerializer(serializers.ModelSerializer):
class Meta:
model = Locations
fields = ('title', 'address', )
class EventsSerializer(serializers.ModelSerializer):
locations = LocationsSerializer(read_only=True)
class Meta:
model = Events
fields = ('locations', )
class EventViewSet(viewsets.ModelViewSet):
queryet = Event.objects.all()
serializer_class = EventsSerializer
def perform_create(self, serializer):
serializer.save(locations=self.request.data['locations'])
def perform_update(self, serializer):
serializer.save(locations=self.request.data['locations'])
A new LocationsSerializer was created, which will handle the read-only nested representation of the Locations object. By overriding perform_create and perform_update, we can pass in the location id that was passed in with the request body, so the location can still be updated.
Also, you should avoid having model names being plurals. It's confusing when Events.locations is a single location, even though Locations.events is a list of events for the location. Event.location and Location.events reads a bit more clearly, the Django admin will display them reasonably, and your fellow developers will be able to easily understand how the relations are set up.

Categories

Resources