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
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).
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'
)
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¶m2=value2 etc.
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?
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',
}
}