Django Model Serializers - python

I'm learning how to use Django Rest Framework and I came out with a question that I can't get to answer.
I understood that the concept of nested serializer but I think there should be a way to get a specific field in an "upper level" instead of "digging" trough levels. Let me better explain what I mean.
I have Users which then are classified into 2 categories Customers (which are the default User category) and Drivers. All the Users have a Profile with their picture so I would like to obtain the name and the image in an API. The only solution I've been managing to find is the following
class OrderProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ("id", "image")
class OrderUserSerializer(serializers.ModelSerializer):
profile = OrderProfileSerializer()
class Meta:
model = User
fields = ("id", "profile")
class OrderCustomerSerializer(serializers.ModelSerializer):
name = serializers.ReadOnlyField(source="get_full_name")
profile = OrderProfileSerializer()
class Meta:
model = User
fields = ("id", "name", "profile")
class OrderDriverSerializer(serializers.ModelSerializer):
name = serializers.ReadOnlyField(source="user.get_full_name")
user = OrderUserSerializer()
class Meta:
model = Driver
fields = ("id", "name", "user")
This is the JSON response
{
"shipping": {
"id": 25,
"customer": {
"id": 14,
"name": "Test Customer",
"profile": {
"id": 10,
"image": "/media/profile_pics/default.jpg"
}
},
"driver": {
"id": 1,
"name": "Test Driver",
"user": {
"id": 28,
"profile": {
"id": 11,
"image": "/media/profile_pics/default.jpg"
}
}
}
}
}
I can make it to work anyway, but I believe that to learn I should always push for a more "elegant" solution rather than "keep the easiest". So I would like to achieve the following response:
{
"shipping": {
"id": 25,
"customer": {
"id": 14,
"name": "Test Customer",
"image": "/media/profile_pics/default.jpg"
},
"driver": {
"id": 1,
"name": "Test Driver",
"image": "/media/profile_pics/default.jpg"
}
}
}
Thank you for any tips/help/suggest that you can provide

Instead of using:
profile = OrderProfileSerializer()
re-write to:
image = serializers.SerializerMethodField()
def get_image(self, obj):
# obj is the customer object
# return what u need (like this example) or write a model inner method
return obj.profile.image
This would align nicely to the json you want
Do the same thing for Driver and User

Related

Update only specific objects

The problem in my code is when I make any updates for my objects especially the Geometry Model. My code will update every object in this Model with the same values. While I am tying is to update each row with its values instead of updating the model with the same values.
I have tried several ways but the problem still occurs.
def update(self, instance, validated_data):
geo = address_data.get('geo')
lat = geo.pop('lat')
lng = geo.pop('lng')
...
gathers = geometry.objects.update(lat=lat, lng=lng)
address = Address.objects.update(address=address, country=country, description=description, geo=gathers )
...
user_data = validated_data.pop('user')
username = user_data.pop('username')
user = User.objects.update(username=username)
gather = userProfile.objects.update(address=address, user=user)
return instance.
class geometry(models.Model):
lat = models.IntegerField(default='')
lng = models.IntegerField(default='')
class Address(models.Model):
...
geo = models.OneToOneField(geometry, on_delete=models.CASCADE, default='')
class userProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, name='user', primary_key=True)
address = models.OneToOneField(Address, on_delete=models.CASCADE, name='address')
The problem is my code updating the whole rows in the model with the same values.
{
"address": {
"id": 1,
"address": "2,lot chragua",
"country": "Morocco",
"description": "dfvfdv",
"geo": {
"id": 1,
"lat": 471,
"lng": 39
}
},
"user": {
"id": 1,
"username": "sdfvedfbf",
"first_name": "",
"last_name": "",
"email": "",
"date_joined": "2019-01-19T11:31:00.415990Z",
"last_login": null
}
},
{
"address": {
"id": 2,
"address": "2.Lot Chraga",
"country": "Morocco",
"description": "sfvsfv",
"geo": {
"id": 2,
"lat": 471,
"lng": 39
}
},
"user": {
"id": 2,
"username": "svscwdc",
"first_name": "",
"last_name": "",
"email": "",
"date_joined": "2019-01-19T11:36:50.266225Z",
"last_login": null
}
}
The .update() method is used to update multiple objects. As you found out, it will update all objects in a queryset.
If I understand you correctly you want to update a single instance, but from your code, it is not clear to me which instance that is.
You can filter the queryset, as #ozcanyarimdunya suggested:
geometry.objects.filter(pk=geo_pk).update(...)
It should be noted that .update() doesn't call .save() on your model or emit any signals. To update a single object, it is usually preferable to assign the new values to the instance and call save, like this:
geo = geometry.objects.get(pk=geo_pk)
geo.lat = lat
geo.lon = lon
geo.save()
On a side note, the common convention is to capitalize class names (Geometry instead of geometry).

Customize the nested data in Serializer

I have a ModelSerializer:
class WorkOrderRetrieveSerializer(ModelSerializer):
workordercomments = WorkOrderCommentForWorkOrderSerializer(many=True, read_only=True)
class Meta:
model = WorkOrder
fields = "__all__"
The JSON data is bellow:
{
"id": 1,
"workordercomments": [
.....
{
"id": 21,
"content": "test files",
"files": "[71]",
"ctime": "2018-01-11T11:03:17.874268+08:00",
"uptime": "2018-01-11T11:03:17.874362+08:00",
"workorder": 1,
"comment_user": {
"id": 5,
"username": "test03",
"is_admin": true
}
}
],
"workorder_num": "WON15118747168252",
"name": "order01",
"content": "first conntetn",
"workordertype": "teck",
"workorder_status": "created",
"user_most_use_email": "lxas#128.com",
"server_id": null,
"public_ip": null,
"belong_area": null,
"files": null,
"ctime": "2017-11-28T21:11:56.826971+08:00",
"uptime": "2017-11-28T21:11:56.827064+08:00",
"to_group": 3,
"user": 2
}
The "files": "[71]", in my JSON is a string of a group contains file ids.
workordercomments is the related-name of the workorder.
I want in the JSON workordercomments shows the files like this:
{
"id": 21,
"content": "test files",
"files": "['/media/images/xxxxx.png']",
"ctime": "2018-01-11T11:03:17.874268+08:00",
"uptime": "2018-01-11T11:03:17.874362+08:00",
"workorder": 1,
"comment_user": {
"id": 5,
"username": "test03",
"is_admin": true
}
}
The "files" value I want to is the link rather than its id.
"files": "['/media/images/xxxxx.png']",
or
"files": ['/media/images/xxxxx.png'],
Is it possible to customize the format? should I come true what function in serializer ?
You need to implement a custom serialzier, as per the docs, and override the default values generated by ModelSerializer.
For example:
from rest_framework.fields import Field
class FileRelatedField(RelatedField):
def to_representation(self, instance):
return instance.file_path # or whereever that path comes from
class WorkOrderRetrieveSerializer(ModelSerializer):
class Meta:
model = WorkOrder
fields = '__all__'
files = FileRelatedField(
many=True,
source='file_set.all'
)
Depending on how your __str__ value is on your File model, you can maybe do this:
from rest_framework.serializers import StringRelatedField
class WorkOrderRetrieveSerializer(ModelSerializer):
class Meta:
model = WorkOrder
fields = '__all__'
files = StringRelatedField(
many=True,
source='file_set.all'
)

Django filters and serializers. Filter model one-to-many nested values

I know how to apply filter to viewset for current model by nested values and return all data concerning to this foreign key.
And that's okay if I have one-to-one relation. But I have one-to-many relation. So I don't know how to filter also these "many values".
Now I have smth like this.
filters.py:
class VersionFilter(FilterSet):
tool = CharFilter(method='tool_filter')
def tool_filter(self, queryset, name, value):
queryset = queryset.filter(changes__tool=value).distinct()
return queryset
class Meta:
model = Version
fields = ('tool')
serializers.py:
class ChangeSerializer(serializers.ModelSerializer):
class Meta:
model = Change
fields = ('tool', 'date', 'type', 'title', 'description')
class VersionSerializer(serializers.ModelSerializer):
changes = ChangeSerializer(many=True, read_only=True)
class Meta:
model = Version
fields = ('date', 'version', 'changes')
viewsets.py:
class VersionViewSet(ReadOnlyModelViewSet):
model = Version
queryset = Version.objects.all()
serializer_class = VersionSerializer
filter_class = VersionFilter
And here is the return for url smth like 127.0.0.0/api/version?tool=General:
"data": [
{
"date": "2017-03-21T10:25:47.848959Z",
"version": "1.12",
"changes": [
{
"tool": "General",
"date": "2017-03-21T10:26:22.838785Z",
"type": "Fix",
"title": "dfa",
"description": ""
},
{
"tool": "General",
"date": "2017-03-21T10:26:08.379112Z",
"type": "Fix",
"title": "dasf",
"description": ""
}
]
},
{
"date": "2017-03-21T10:33:43Z",
"version": "1.01.12",
"changes": [
{
"tool": "General",
"date": "2017-03-21T10:44:35.143232Z",
"type": "Improvement",
"title": "qw",
"description": ""
},
{
"tool": "Costs",
"date": "2017-03-21T10:34:12.482258Z",
"type": "Fix",
"title": "dfaasss",
"description": ""
}
]
}
]
So, versions were filtered by tool "General" (it's not visible here, but one version is out). But due to serializer, tool "Costs" is still in the query (and I want it to be removed).
How can I achieve it?
class VersionFilter(FilterSet):
tool = CharFilter(method='tool_filter')
def tool_filter(self, request, queryset):
# get all values matching query params
tool = request.GET.get('tool')
date = request.GET.get('date')
type = request.GET.get('type')
title = request.GET.get('title')
description = request.GET.get('description')
data = {
"tool": tool,
"date": date,
"type": type,
"title": title,
"description": description
}
arguments = {}
# filter out values that evaluate to false e.g. None, ''
for k, v in data.items():
if v:
arguments[k] = v
# run query
queryset = queryset.objects.filter(**arguments)
return queryset
The code is untested but it is very useful if you want to return filters in query params like containing localhost:8000/?param1=value1&param2=value2 etc.

Django REST Framework: Nested relationships only on read

I have a few related models and I wanted to have related object information nested into the read so I followed the example outlined here:
http://www.django-rest-framework.org/api-guide/relations/#nested-relationships
A couple of my serializers look like this:
class ShowSerializer(serializers.ModelSerializer):
artists = ArtistSerializer(many=True)
venue = VenueSerializer()
class Meta:
model = Show
class GuestSerializer(serializers.ModelSerializer):
show = serializers.RelatedField()
class Meta:
model = Guest
And when I read them (GET), they're perfect, the output is something like:
"results": [
{
"id": 1,
"show": {
"id": 2,
"artists": [
{
"id": 13,
"name": "Some Artist"
}
],
"venue": {
"id": 3,
"name": "Some Venue",
},
"date": "2015-11-20"
},
"first_name": "Amanda",
"last_name": "Hugankiss",
}
]
The problem I'm having is when I POST, this configuration seems to expect an entire nested structure just as above, where I'd prefer to specify an id for a relationship on POST, like so:
{
"show": 16,
"first_name": "Maya",
"last_name": "Normusbutt",
}
Is there a way to configure my serializers/view (by the way, I'm using ModelViewSet) to do this?

Where to change the form of json response in django rest framework?

Lets say I have a model:
class MyModel(models.Model):
name = models.CharField(max_length=100)
description= models.TextField()
...
Then I created ModelViewSet with HyperLinkedSerializer, so when I call my /api/mymodels endpint I get responses like this:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{ "name": "somename", "description": "desc"},
{ "name": "someothername", "description": "asdasd"},
]
}
and when I call /api/mymodels/1 I get:
{ "name": "somename", "description": "asdasd"}
but what I would like to get is:
{
"metadata":{ ...},
"results": { "name": "somename", "description": "desc"}
}
And I would like to use this format for all models at my website, so I dont want to change every viewset, I want to implement it in (most likely) one class and then use it for all my viewsets.
So my question is: which renderer or serializer or other class (Im really not sure) should I alter or create to get this behavior of json response?
The first response appears to be a paginated response, which is determined by the pagination serializer. You can create a custom pagination serializer that will use a custom format. You are looking for something similar to the following:
class MetadataSerialier(pagination.BasePaginationSerializer):
count = serializers.Field(source='paginator.count')
next = NextPageField(source='*')
previous = PreviousPageField(source='*')
class CustomPaginationSerializer(pagination.BasePaginationSerializer):
metadata = MetadataSerializer(source='*')
This should give you an output similar to the following:
{
"metadata": {
"count": 2,
"next": null,
"previous": null
},
"results": [
{ "name": "somename", "description": "desc"},
{ "name": "someothername", "description": "asdasd"}
]
}
The pagination serializer can be set globally through your settings, as described in the documentation.
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_SERIALIZER_CLASS': {
'full.path.to.CustomPaginationSerializer',
}
}

Categories

Resources