DRF serialize PK Field - django

-Solved-
This request was never going to work as some servers and browsers don't process body params from a GET request however Postman simply processes the request as expected and this was the reason why I got very confused and not sure what to look for. Instead use query params if you wish to filter data in a REST
-updated for more clarification-
I have 2 models CustomUser and Category, added User FK to Categories
updated my serializers to account for that ( not sure this part is correct tho )
on a Ajax GET request I pass the user id so I can grab only the categories that have the specified FK
in my Views request.get.data('user_id') brings back None
if I run the same request via Postman then I get the desired results
Any clues on why this would happen?
Apologies if my initial post was vague.
class CategorySerializer(TaggitSerializer, serializers.ModelSerializer):
author = serializers.PrimaryKeyRelatedField(queryset=CustomUser.objects.all())
posts = PostSerializer(many=True, read_only=True)
tags = TagListSerializerField()
class Meta:
model = Category
fields = ('id', 'name', 'description', 'created_at', 'updated_at', 'posts', 'tags', 'author')
# User Serializer
class CustomUserSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ('id', 'email')

I didn't get clear, do you have trouble reading the id in the response or writing it to the database?

Related

Django REST Framework: nested relationship, how to submit json?

I'm using Django 2.1, DRF 3.7.7.
I've some models and their relative (model) serializers: these models are nested, and so are the serializers.
Let me give an example:
# models.py
class City(models.Model):
name = models.CharField(max_length=50)
class Person(models.Model):
surname = models.CharField(max_length=30)
birth_place = models.ForeignKey(City)
# serializers.py
class CitySerializer(serializers.ModelSerializer):
class Meta:
model = models.CitySerializer
fields = "__all__"
class PersonSerializer(serializers.ModelSerializer):
birth_place = CitySerializer()
class Meta:
model = models.Person
fields = "__all__"
If I submit an AJAX request with a json like:
{'surname': 'smith', 'birth_place': 42}
I get back a Bad Request response, containing: Invalid data. Expected a dictionary, but got int.
If I submit a nested json like:
{'surname': 'smith', 'birth_place': {'id': 42, 'name': 'Toronto'}}
the relation is not converted, the id field is ignored and the rest is parsed to:
OrderedDict([('birth_place', OrderedDict([('name', 'Toronto')]))])
The following is the post method I'm using on a class-based view:
def post(self, request):
print("Original data:", request.data)
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
self.data = serializer.validated_data
print("Parsed data:", self.data)
...
I only need to get data from the endpoints connected to the serializers, I don't need to write/save anything through the REST interface, since the POST processing of the form is done by Django.
TL;DR: How should I correctly submit a JSON request to a nested serializer, without having to write handmade conversions? Did I commit errors in setting up the serializers?
Edit: I've discovered that by adding id = serializers.IntegerField() to the serializer parent class (e.g. City), the serializer parser now processes the id. At least now I'm able to perform actions in the backend with django.
Generic writing for nested serializers is not available by default. And there is a reason for that:
Consider, you are creating a person with a birthplace, using a POST request. It is not clear if the submitted city is a new one or an existing one. Should it return an error if there isn't such a city? Or should it be created?
This is why, if you want to handle this kind of relationship in your serializer, you need to write your own create() and update() methods of your serializer.
Here is the relevant part of the DRF docs: http://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers
It's definitely not clearly put into the docs of django-rest. If you follow the process of serializers processing the data for creation then it becomes clear that django manages m2m by saving the parent instance first and then adding the m2m values, but somehow the m2m fields don't go through the validation if you mark them as read_only.
The solution to this is to overr run_validation method of the serializer. The serializer should look like this:
class ExampleSerializer(serializers.ModelSerializer):
queryset = SomeModel.objects.all()
tags = TagSerializer(many=True, read_only=True)
class Meta:
model = SomeModel
fields = ['pk', 'name', 'tags']
def run_validation(self, data):
validated_data = super(StudyResourceSerializer, self).run_validation(data)
validated_data['tags'] = data['tags']
return validated_data
The request body should look like this:
{
"tags": [51, 54],
"name": "inheritance is a mess"
}

How can I filter DRF serializer HyperlinkedRelationField queryset based on request data?

I have my users account authority stored in request.session.get('authority')
At the moment the endpoint in DRFs web-browsable HTML representation is showing all the addresses of all accounts in the html form. Which I'd expect as I'm querying all the addresses.
Screenshot of DRF form: addresses are displaying their authority.uuid, they should only show the current authorities address data.
Company Serializer
# ToDo: filter queryset objects
class CompanySerializer(serializers.ModelSerializer):
clients = serializers.HyperlinkedRelatedField(
many=True,
view_name='client-detail',
queryset=Client.objects.all()
)
addresses = serializers.HyperlinkedRelatedField(
many=True,
view_name='address-detail',
queryset=Address.objects.all()
)
class Meta:
model = Company
fields = ('name', 'url', 'clients', 'addresses')
read_only_fields = ('authority',)
I want to be able to do something like:
addresses = serializers.HyperlinkedRelatedField(
many=True,
view_name='address-detail',
queryset=Address.objects.filter(authority=request.session.get('authority'))
)
But not sure there is a way to access the request data in the serializer when I'm setting up the HyperlinkedRelatedField.
Perhaps I'm approaching this in entirely the wrong way. Any guidance will be greatly appreciated.
Update
Many thanks to Enthusiast Martin, based on his answer this is how I've implemented it for now:
def hyperlinkedRelatedFieldByAuthority(model, view_name, authority):
return serializers.HyperlinkedRelatedField(
many=True,
view_name=view_name,
queryset=model.objects.filter(authority=authority)
)
class CompanySerializer(serializers.ModelSerializer):
def get_fields(self):
fields = super().get_fields()
authority = self.context['request'].session.get('authority')
fields['clients'] = hyperlinkedRelatedFieldByAuthority(Client, 'client-detail', authority)
fields['addresses'] = hyperlinkedRelatedFieldByAuthority(Address, 'address-detail', authority)
return fields
You can override serializer's get_fields method and update the addresses queryset.
You can access the request via serializer's context
Something like this:
def get_fields(self):
fields = super().get_fields()
request = self.context['request']
fields['addresses'].queryset = ** your queryset using request data **
return fields

Add a set of related models to serializer output in django w djange-rest-framework

So I am just getting familiar with django and django-rest-framework.
I have a model of Jobs that have an owner, and I want to add the list of jobs a user owns to the user endpoint.
I tried to do it like this, as the tutorial of the django rest framework says:
class UserSerializer(serializers.ModelSerializer):
jobs = serializers.PrimaryKeyRelatedField(many = True, queryset = Job.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'jobs')
But for some reason, when I want to view the users, I recieve
AttributeError at /users/
'User' object has no attribute 'jobs'
The documentation about the PrimareyKeyRelatedField looks just like this. Do I maybe have to do something with the user model?
What works is the following:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'username', 'job_set')
but I don't like that solution as I want to have more control about how the list is presented (maybe change the ids to complete json objects or rename the attribut name from 'job_set' to 'job)
I really don't now what I am overseeing...
You can do two things,
jobs = serializers.PrimaryKeyRelatedField(many = True, source='job_set' queryset = Job.objects.all())
or
Set related_name='jobs' attribute to User relational field.

Django REST framework and creating two table entries from a POST form

I want to create entries in two tables (Log and Post) using the DRF Browseable API POST form.
The example below is contrived, but it outlines what I am trying to do.
class Post(models.Model):
info = models.CharField()
class Log(TimeStampedModel):
ip = models.GenericIPAddressField(('IP Address'))
name = models.CharField()
data = models.ForeignKey(Post)
I want to use the browseable API to submit a form to create a Log entry. Here are the serializers:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('info',)
class LogSerializer(serializers.ModelSerializer):
data = serializers.Field()
class Meta:
model = Log
fields = ('ip', 'name', 'data')
The problem with the above is that serializer.Field is read only so does not show up on the POST form. If I change it to CharField it shows up, but then I get an error because an instance of a Post is expected not just a field of the Post object.
Here are my views:
class LogMixin(object):
queryset = Log.objects.all()
serializer_class = LogSerializer
class LogList(LogMixin, ListCreateAPIView):
pass
class LogDetail(LogMixin, RetrieveUpdateDestroyAPIView):
pass
What's the correct way of doing this?
From what I can tell you want to create a nested Log object. There are 2 ways of doing this:
Send 2 POST Requests, One to create the Post, and the other to create the Log contained the received HTTP 200 data from the API.
(Django and best way) Send the data all in one POST and parse it server side. Django Rest Framework takes care of this for you.
I have changed your code so that it should work.
Source
class LogSerializer(serializers.ModelSerializer):
class Meta:
model = Log
fields = ('ip', 'name')
class PostSerializer(serializers.ModelSerializer):
log = LogSerializer()
class Meta:
model = Post
fields = ('info', 'log')
views.py
import generics
class PostCreateAPIView(generics.CreateAPIView):
model = Post
serializer_class = PostSerializer
Then you can send a POST Request containing 'info', 'ip', and 'name'.
This is a hacky way and the best way to use the nested serializer as stated above. But just to show another way I am posting it here.
# Add New Item
#api_view(('POST',))
def add_new_item(request):
request.data['areaname_name'] = request.data['item_areaname']
request.data['buildingname_name'] = request.data['item_buildingname']
item_serializer = TblItemSerializer(data=request.data)
area_name_serializer = TblAreanameSerializer(data=request.data)
building_name_serializer = TblBuildingnameSerializer(data=request.data)
response = []
if item_serializer.is_valid() & area_name_serializer.is_valid() & building_name_serializer.is_valid():
# Save To Item Table
item_serializer.save()
# Save Unique Values Into Areaname and Buildingname Tables
area_name_serializer.save()
building_name_serializer.save()
return Response(item_serializer.data, status=status.HTTP_201_CREATED)
else:
response.append(item_serializer.errors)
response.append(area_name_serializer.errors)
response.append(building_name_serializer.errors)
return Response(response, status=status.HTTP_400_BAD_REQUEST)
In the error response you could also use (depending on how you want to handle on client side)
merge = {**item_serializer.errors, **area_name_serializer.errors, **building_name_serializer.errors}
return Response(merge, status=status.HTTP_400_BAD_REQUEST)

Django Rest Framework what is the best way to show more or less models fields in the response, base on the user request that makes the cal

I have several Apps serving a nice restful API with Django Rest Framework, love it.
My questios is, what is the best way to show more or less models fields in the response, base on the user request that makes the call, Thanks very much!, on this example I show you mi implementation, but doesn't seem very dry
In my views I need to decide what fields to show the user depending on the request User, so I have the following example:
class PostRetrieveUpdateDestroyAPIView(generics.RetrieveUpdateDestroyAPIView):
model = Post
permission_classes = (IsOwnerOrAuthenticatedReadOnly,)
def get_serializer_class(self):
obj = self.get_object()
if self.request.user == obj.owner:
if obj.draft:
return LoggedUserPostDraftWriteSerializer
return LoggedUserPostDraftReadSerializer
return PostBaseSerializer
And the example of two serializer is:
class PostBaseSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields = ('slug', 'title', 'content')
read_only_fields = ('slug',)
class LoggedUserPostDraftReadSerializer(AnyModelBaseSerializer):
class Meta:
model = AnyModel
fields = ('slug', 'title', 'content', 'owner', 'is_draft', 'more_fields')
read_only_fields = ('slug',)
I feel that I am not doing this right, should I attack this problem from the serializer point of view?
my questios is, what is the best way to show more or less models fields in the response, base on the user request that makes the call, Thanks very much!
Maybe a bit late, but I think this link is exactly what you need.
It explains how to build a dynamic serializer, which can take an extra argument 'fields', so that it can only return the fields you need.