Nested write serializer validated_date is empty - django-rest-framework

Followed the best practive create nested objects with serializers, however I still receive empty nested validate_data.
Serializers:
class WriteOrganisationSiteSerializer(serializers.ModelSerializer):
"""Organisation Site serializer class for post methods."""
site = WriteAPSiteSerializer()
class Meta:
model = models.OrganisationSite
fields = ("organisation", "site")
def create(self, validated_data):
from fadat.sites.models import APSite
site_data = validated_data.pop("site")
activeplaces_site_id = site_data.pop("activeplaces_site_id")
site, created = APSite.objects.get_or_create(
activeplaces_site_id=activeplaces_site_id, defaults=site_data
)
organisation_site = models.OrganisationSite.objects.create(
site=site, **validated_data
)
return organisation_site
class WriteAPSiteSerializer(serializers.Serializer):
"""Active Places Site serializer class for post methods."""
class Meta:
model = models.APSite
fields = (
"activeplaces_site_id",
"site_name",
"dependent_thoroughfare",
"thoroughfare_name",
"double_dependent_locality",
"dependent_locality",
"post_town",
"postcode",
"easting",
"northing",
"longitude",
"latitude",
)
The view
class OrganisationSitesView(APIView):
"""Organisation Sites API view."""
def post(self, request, **kwargs):
user = request.user
ser = serializers.WriteOrganisationSiteSerializer(data=request.data)
ser.is_valid(raise_exception=True)
obj = ser.save()
ser = serializers.ReadOrganisationSiteSerializer(obj)
return Response(ser.data, status=201)
Running the following test (or via browser a ajax request, same result)
def test_add_organisation_site(self):
user = User.objects.create(email="newbie#dat.example.com")
organisation_type = OrganisationType.objects.create(name="Club")
organisation = Organisation.active_objects.create(
name="Club", organisation_type=organisation_type
)
site = {
"activeplaces_site_id": 1200341,
"site_name": "CITY OF LONDON SCHOOL",
"dependent_thoroughfare": "",
"thoroughfare_name": "QUEEN VICTORIA STREET",
"double_dependent_locality": "",
"dependent_locality": "",
"post_town": "LONDON",
"postcode": "EC4V 3AL",
"easting": 531990,
"northing": 180834,
"longitude": -0.099387,
"latitude": 51.511025,
}
body = {
"organisation": organisation.id,
"site": site,
}
self.authenticate(user)
url = reverse("api:inspections:organisation-sites")
res = self.client.post(url, json.dumps(body), content_type="application/json; charset=utf-8")
self.assertEqual(res.status_code, 201)
Receiving the following headers in my views
{'organisation': 1, 'site': {'activeplaces_site_id': 1200341, 'site_name': 'CITY OF LONDON SCHOOL', 'dependent_thoroughfare': '', 'thoroughfare_name': 'QUEEN VICTORIA STREET', 'double_dependent_locality': '', 'dependent_locality': '', 'post_town': 'LONDON', 'postcode': 'EC4V 3AL', 'easting': 531990, 'northing': 180834, 'longitude': -0.099387, 'latitude': 51.511025}}
In the view the request.data show me
{'Cookie': '', 'Content-Length': '368', 'Content-Type': 'application/json; charset=utf-8', 'Authorization': 'Token 0081d8a36d90f1d922a2a7df494afe127a220495'}
Still the serialized doesn't validate nested fields and returns
{'organisation': <Organisation: Club (Club)>, 'site': OrderedDict()}

Right, here's what I see that needs fixing: the conventional way to design a post()method is as follows:
def post(self, request, **kwargs):
serializer = serializers.WriteOrganisationSiteSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
Although your case is in many ways equivalent, it's obviously not 100% correct. Perhaps it has to do with the fact that you're using one serializer to create the new instance, and another to show the result, when that's not needed: you can set up which fields are read_only, which ones are write_only, and those that are both read and write, as the documentation explains. I suggest you follow the schema I laid out above to guide you, and define in WriteOrganisationSiteSerializer how to show the data to the end user. Do let me know if you have any problems.

Related

are django querysets fastest?

models.py
class Comments(UUID):
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
post = models.ForeignKey(Posts, on_delete=models.CASCADE)
comment = models.ForeignKey(
"self", on_delete=models.CASCADE, blank=True, null=True
)
text = models.TextField()
files = models.ImageField()
serializers.py
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = Comments
fields = "__all__"
extra_kwargs = {
"user": {"read_only": True},
"text": {"required": False},
"files": {"required": False}
}
def create(self, validated_data):
user = self.context["request"].user
return Comments.objects.create(user=user, **validated_data)
views.py
class PostCommentsRetrieveAPIView(generics.RetrieveAPIView):
serializer_class = CommentSerializer
def get(self, request, *args, **kwargs):
replies_count = Subquery(Comments.objects.filter(
comment=OuterRef("pk")
).values("comment").annotate(count=Count("pk")).values("count"))
comment_remarks = Subquery(Remarks.objects.filter(
comment=OuterRef("pk")
).values("comment").annotate(count=Count("pk")).annotate(
popularities=popularities
).values("popularities"))
# https://stackoverflow.com/questions/63020407/return-multiple-values-in-subquery-in-django-orm
replies = Subquery(Comments.objects.filter(
comment=OuterRef("pk")
).values("comment", "created_at", "updated_at").annotate(
created=Now() - F("created_at"), created_=created_,
updated=Now() - F("updated_at"), updated_=updated_
).annotate(
details=ArrayAgg(
JSONObject(
id="id",
user_id="user_id",
username="user__profile__username",
text="text",
files="files",
created="created_",
updated="updated_",
profile_link=profile_link,
profile_image=profile_picture,
comment_remarks=comment_remarks,
replies_count=replies_count
)
)
).values("details"))
comment = Subquery(Comments.objects.filter(
post=OuterRef("pk"), comment=None
).values("post", "created_at", "updated_at").annotate(
created=Now() - F("created_at"), created_=created_,
updated=Now() - F("updated_at"), updated_=updated_
).values("created_").annotate(
details=ArrayAgg(
JSONObject(
id="id",
user_id="user_id",
username="user__profile__username",
text="text",
files="files",
created="created_",
updated="updated_",
profile_link=profile_link,
profile_image=profile_picture,
comment_remarks=comment_remarks,
comment_replies=replies
)
)
).values("details"))
post = Posts.objects.filter(id=kwargs["post_id"]).annotate(
comment=comment,
).values("comment")
return Response(post, status=status.HTTP_200_OK)
Now I just want to ask is that a best way show comments on post with nested replies, I create another files to calculate datetime, profile link, profile image, remarks on comment
this query is hitting database only one time my question is will it be fastest query to get data from database?
how to can I check that my query is fastest or not

Handle complex request formats and return response

I am building an API using Django rest framework. My API has to receive complex requests that are somewhat different from my models. More specifically the request has to include fields that do not exist in my models.
As an example I have included one of the models and the request that will be received by the API.
from django.db import model
class Author(models.Model):
first_name = models.CharField(max_length=9, blank=False)
last_name = models.CharField(max_length=9, blank=False)
birth_year = mmodels.CharField(max_length=9, blank=False)
and the json request is
{
"general": {
"name": "John",
"surname": "Doe"
},
"details": [
"1980"
]
}
How can I parse that request efficiently, store the data in my database and finally return a response similar to the request?
My approach so far is to create a serializer like the following and modify its create() and to_represent() methods, however this approach seems very dirty especially with nested relationships.
class AuthorSerializer(serializers.ModelSerializer):
general = serializers.DictField(write_only=True)
details = serializers.ListField(write_only=True)
class Meta:
model = Author
fields = [
"id", "general", "details",
]
def create(self, validated_data):
author_data = {}
author_data['first_name'] = validated_data['general']['name']
author_data['last_name'] = validated_data['general']['surname']
author_data['birth_year'] = validated_data['details'][0]
author = Author.objects.create(**author_data)
return author_data
def to_representation(self, instance):
final_representation = OrderedDict()
final_representation["id"] = instance.id
final_representation["general"] = {}
final_representation["general"]["name"] = instance.first_name
final_representation["general"]["surname"] = instance.last_name
final_representation["details"] = [instance.birth_year]
return final_representation

Alter Queryset before returning in DRF

I have a model structure similar to the one below:
Store -> some store fields
Books -> some book fields, FK to Store
BookProperty -> name, value, FK to Books (a one to many relationship), FK to store
The book property can store any info for the book eg. no_of_pages, publisher etc added by the store.
I need to make an API endpoint where I can get all BookProperty for a store.
I used the url:
/stores/:store_id/bookproperty
Used a ModelSerializer for BookProperty with fields = [publisher, no_of_pages]
Used a genericViewSet with a ListModelMixin.
The endpoint turned out like this below:
{
"count": 4,
"next": null,
"previous": null,
"results": [
{
"name": "publisher",
"value": "somePublisher"
},
{
"name": "pages",
"value": "1000"
},
{
"name": "publisher",
"value": "someOtherPublisher"
},
{
"name": "publisher",
"value": "somePublisher"
}
]
}
The problem with this is that multiple objects can have the same name, value pairs. I need this information in a way where all the objects are unique and grouped kind of like this:
{
{"name":"publisher", "value":["somePublisher", "someOtherPublisher"]},
{"name":"pages", "value":["1000"]},
}
I'm trying to override the get_queryset(self) but it's not working.
Any help would be appreciated. Thanks in advance!
EDIT:
models.py
class BookProperty(models.Model):
books = models.ForeignKey(
Books,
on_delete=models.CASCADE,
)
name = models.CharField(max_length=100)
value = models.CharField(max_length=100)
store = models.ForeignKey(
"Store",
on_delete=models.CASCADE,
)
serializers.py
class BookPropertySerializer(serializers.ModelSerializer):
class Meta:
model = models.BookProperty
fields = ["name", "value"]
views.py
class BookPropertyViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
serializer_class = serializers.BookPropertySerializer
I think that instead of overriding the get_queryset(self) I should try changing the def list(self, request, *args, **kwargs) :
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
tag_dictionary = defaultdict()
things = list(queryset)
for key, group in itertools.groupby(things, lambda x: x.name):
for thing in group:
if key not in tag_dictionary.keys():
tag_dictionary[key] = [thing.value]
else:
tag_dictionary[key].append(thing.value)
for key in tag_dictionary.keys():
tag_dictionary[key] = list(set(tag_dictionary[key]))
return Response(json.dumps(tag_dictionary))
The above solution is working but might not be the best one.

How to filter records based on nested data using django rest framework

I have this json response from my customer api based on django rest framework.
When I hit api
http://localhost:8000/api/customers
I receive following response
[
{
'name': 'Daniel',
'group': 'BK',
'user_id': 102,
'user_details': {
'username': 'dan1',
'active': true,
}
},
{
'name': 'John',
'group': 'BK',
'user_id': 103,
'user_details': {
'username': 'john1',
'active': true,
}
}
]
Now I need to filter record whose username=john1, how do I do that?
I have tried using this in my customer viewset by defining filter backend
filter_fields = ('user_details__username',)
and tried hitting the api as
http://localhost:8000/api/customers?user_details__username=john1
but it gives error as
'Meta.fields' contains fields that are not defined on this FilterSet:
user_details__username
Its happening because user_details is not the field of my customer serializer, its basically SerializerMethodField which manipulates user information to display under customer api.
Here is my customer serializer
class CustomerSerializer(serializers.HyperlinkedModelSerializer):
user_details = serializers.SerializerMethodField('get_serialized_target_object')
class Meta:
model = Customer
fields = '__all__'
def get_serialized_target_object(self, obj):
usr_id = obj.user_id
if usr_id:
instance = User.objects.filter(pk=usr_id)
if instance:
instance = instance[0]
return UserSerializer(instance=instance).data
else:
return None
and here is my viewset
class CustomerViewSet(viewsets.ModelViewSet):
queryset = Customer.objects.all()
serializer_class = CustomerSerializer
filter_fields = ('user_details__username',)
Please help me how do I filter my record from customer api with username=john1
You should not use SerializerMethodField.
Try the serializer below:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'active',)
class CustomerSerializer(serializers.ModelSerializer):
user_details = UserSerializer(many=True)
class Meta:
model = Customer
fields = '__all__'
Then you can define your field as filter_fields = ('user_details__username',)

Django Rest Framework - can't created object with existing FK reference

I have a serializer class in DRF like so:
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
created_by = UserSerializer() # created_by is an FK to User model
class Meta:
model = Project
fields = ('id', 'title', 'created_by')
My views:
class ProjectList(generics.ListCreateAPIView):
model = Project
serializer_class = ProjectSerializer
filter_fields = ('title',)
def post(self, request, format=None):
serializer = ProjectSerializer(data={
"id": 12,
"title": "FooBar's Project",
"created_by": {
"id": 1,
"username": "foobar",
"first_name": "foo",
"last_name": "bar",
},
})
if serializer.is_valid():
serializer.save()
else:
print serializer.errors
return Response(serializer.data, status=status.HTTP_201_CREATED)
This works almost as expected except that DRF complains that:
{'created_by': [{'username': [u'User with this Username already exists.']}]}
What I want is that the Project is created with reference to an existing user. What am I doing wrong here?
Note: I am tracking trunk for DRF, not from the cheeseshop.
If you want to assign an existing user, not creating one, nested serializer is not the way. As you want to just add the relation to the user, you will need to take a look at relations.
The PrimaryKeyRelatedField is way to go and since this field is going to be writable you will also need to pass a queryset attribute.
created_by = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
See the documentation to check the additional options that can be passed to this field:
http://django-rest-framework.org/api-guide/relations#primarykeyrelatedfield
Then your example post could look like this:
def post(self, request, format=None):
user = User.objects.get(pk=8)
serializer = ProjectSerializer(data={
"id": 12,
"title": "FooBar's Project",
"created_by": user.pk,
})
(Posted on behalf of the question author).
This is the working solution:
# serializers.py
class ProjectSerializer(serializers.HyperlinkedModelSerializer):
...
created_by = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all()
)
class Meta:
model = Project
fields = ('id', 'title', 'created_by')
# views.py
class ProjectList(generics.ListCreateAPIView):
model = Project
serializer_class = ProjectSerializer
filter_fields = ('title',)
def post(self, request, format=None):
serializer = ProjectSerializer(data={
"title": request.DATA.get('title'),
"created_by": request.user.pk,
})
if serializer.is_valid():
serializer.save()
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.data, status=status.HTTP_201_CREATED)

Resources