Django Rest Framework - mapping serializers fields to a database column name - django

I am dealing with Django Rest Framework project and the generic response for my view is not what the app client expects.
The app client expects that the filed of the related models, appears as they are in the database. Example given: model City has a foreign key to Country model, represented by a country_id column.
Is there any option to "map" the serializers default fields into a custom one? I have check the Django Rest Framework documentation but I only found "serializer_field_mapping" but I don't know if it will fit my requirements and also I don't know how to use it.
Somehow I got a close approach of it, but only in the case for fetching data --creating / updating threw some errors that I did not get how to manage. :(
Bellow I attach my models.py file, plus the actual output and the desired output. Also, if it is possible, I would like to retrieve data related with Country / Region if exists combined with the database column field_id names.
Thanks in advance,
models.py
from django.db import models
from django.contrib.postgres.fields import ArrayField
class Country(models.Model):
name = models.CharField(max_length=150, unique=True, blank=False)
class Meta:
db_table = 'countries'
class Region(models.Model):
name = models.CharField(max_length=150, unique=True, blank=False)
code = models.CharField(max_length=150, blank=True)
class Meta:
db_table = 'regions'
class City(models.Model):
country = models.ForeignKey(Country)
region = models.ForeignKey(Region, null=True, blank=True, default=None)
name = models.CharField(max_length=150, unique=True, blank=False)
postal_codes = ArrayField(models.CharField(max_length=12, blank=True), null=True, blank=True, default=None)
def __str__(self):
if not self.region:
return '%s (%s)' % (self.name, self.country.name)
return '%s, %s (%s)' % (self.name, self.region.name, self.country.name)
class Meta:
db_table = 'cities'
Actual Output:
[
{
"id": 1,
"name": "San Francisco",
"postal_codes": null,
"country": 1,
"region": 1
},
{
"id": 2,
"name": "Palo Alto",
"postal_codes": null,
"country": 1,
"region": 1
},
{
"id": 3,
"name": "New York City",
"postal_codes": null,
"country": 1,
"region": 2
},
{
"id": 4,
"name": "London",
"postal_codes": null,
"country": 2,
"region": null
}
]
Desired Output:
[
{
"id": 1,
"country_id": 1,
"region_id": 1,
"name": "San Francisco",
"postal_codes": null
},
{
"id": 2,
"country_id": 1,
"region_id": 1,
"name": "Palo Alto",
"postal_codes": null
},
{
"id": 3,
"country_id": 1,
"region_id": 2,
"name": "New York City",
"postal_codes": null
},
{
"id": 4,
"country_id": 2,
"region_id": null,
"name": "London",
"postal_codes": null
}
]

You can use the source parameter of a field on your serializer to achieve this. For example:
from rest_framework import serializers
from .models import City
class CitySerializer(serializers.ModelSerializer):
country_id = serializers.IntegerField(source='country')
region_id = serializers.IntegerField(source='region')
class Meta:
model = City
fields = ('id', 'country_id', 'region_id', 'name', 'postal_codes')
EDIT: As Yaroslav pointed out, when doing it in this way, you don't need to include the source. Take note, however, that simply including country_id or region_id in the fields list is not sufficient. You still need to specify the field on the serializer, such as country_id = serializers.IntegerField() and also include it in the fields.

Thanks to all. Finally I got it. See code bellow. I answer my question due to there is no option to add large amount of lines on a answer comment.
serializers.py
from rest_framework import serializers
from .models import Country, Region, City
class CountrySerializer(serializers.ModelSerializer):
class Meta:
model = Country
fields = ('id', 'name')
class RegionSerializer(serializers.ModelSerializer):
class Meta:
model = Region
fields = ('id', 'name', 'code')
class CitySerializer(serializers.ModelSerializer):
country_id = serializers.PrimaryKeyRelatedField(
queryset=Country.objects.all(),
required=True,
source='country',
)
region_id = serializers.PrimaryKeyRelatedField(
queryset=Region.objects.all(),
allow_null=True,
required=False,
source='region',
)
country = CountrySerializer(
read_only=False,
required=False,
)
region = RegionSerializer(
required=False,
allow_null=True,
read_only=True,
)
class Meta:
model = City
fields = (
'id',
'country', 'region',
'name', 'postal_codes',
'country_id', 'region_id',
)

Related

How to get all related objects from a reverse foreign key Django serializer (one-to-many)?

I have been trying to create a Trello-like clone and storing my 'Lists' and 'Applications' as separate models with a foreign key on my 'Applications' model to create a One-To-Many between Lists and Applications. I usually use Node and Sequelize and have been unsuccessful in trying to query my Lists and also returning all the Applications with the List's ID as the Applications foreign key. I suspect I am just missing something silly on my Serializers.
I've tried a few things that broke the code, but now I just have it returning the List's fields like this:
[
{
"title": "My First List"
},
{
"title": "My Second List"
},
]
when in reality what I really want back is something like:
[
{ "id": 1,
"title": "My First List",
"applications": [
{
"id": 1,
"company_name": "Spotify",
"position": "Web Engineer",
"date_applied": "2019-09-09",
"application_id": "xv112_cv",
"priority_level": "High",
"company_contact_email": "jg#spotify.com",
"notes": "Seems promising",
"location": "New York",
"status_list": "1"
},
{
"id": "2",
"company_name": "Foursquare",
"position": "Client Solutions Engineer",
"date_applied": "2019-10-09",
"application_id": "fsq_app_1",
"priority_level": "High",
"company_contact_email": "jdwyer#foursquare.com",
"notes": "Interview on 9/29/19",
"location": "New York",
"status_list": "1"
},
]
},
{
"id": "2"
"title": "My Second List",
"applications": "applications": [
{
"id": "3",
"company_name": "Etsy",
"position": "Web Engineer",
"date_applied": "2019-09-09",
"application_id": "12345",
"priority_level": "High",
"company_contact_email": "",
"notes": "Seems promising",
"location": "New York",
"status_list": 2
}
]
}
]
Here are my models:
class List(models.Model):
title = models.CharField(max_length=100)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Application(models.Model):
_list = models.ForeignKey(
List, related_name='list', on_delete=models.CASCADE, null=True
)
company_name = models.CharField(max_length=100)
position = models.CharField(max_length=100)
date_applied = models.DateField(
default=date.today(), blank=True)
application_id = models.CharField(blank=True, max_length=100)
[ . . . ]
And my Serializers are here:
class ListSerializer(serializers.ModelSerializer):
applications_set = ApplicationSerializer(read_only=True, many=True)
class Meta:
model = List
fields = ('title', 'applications_set',)
depth = 1
class ApplicationSerializer(serializers.ModelSerializer):
class Meta:
model = Application
fields = '__all__'
I know that I don't have an 'applications' or 'applications_set' field on my List model, but I figured it's not necessary due to the fkey association defined.
Is what I am trying to do possible?? What am I missing?
Also for reference are my viewsets:
class AllListsViewSet(viewsets.ModelViewSet):
queryset = List.objects.all()
permission_classes = [permissions.AllowAny]
serializer_class = ListSerializer
class ApplicationViewSet(viewsets.ModelViewSet):
queryset = Application.objects.all()
permission_classes = [
permissions.AllowAny
]
serializer_class = ApplicationSerializer
EDIT:
Figured it out - on my Application model I just made a small tweak to the related_name attribute:
status_list = models.ForeignKey(
List, related_name='applications', on_delete=models.CASCADE, null=True
)
and on my ListSerializer:
class ListSerializer(serializers.ModelSerializer):
applications = ApplicationSerializer(read_only=True, many=True)
class Meta:
model = List
fields = ('title', 'applications',)
You've given your FK a related_name. That overrides application_set. You've called it list, which is not at all a good name, but since you've set it that is what you need to use as the field name in the serializer.
I would recommend removing that related_name attribute - and also renaming the FK itself, you shouldn't have an underscore prefix in a field name.

Add related ForeignKey fields with serializer in Django REST Framework

I'm using Django 2.2 and Django REST Framework
I have three models like
class Plan(models.Model):
name = models.CharField(_('Plan Name'), max_length=100)
default = models.NullBooleanField(default=None, unique=True)
created = models.DateTimeField(_('created'), db_index=True)
quotas = models.ManyToManyField('Quota', through='PlanQuota')
class Quota(models.Model):
codename = models.CharField(max_length=50, unique=True)
name = models.CharFieldmax_length=100)
unit = models.CharField(max_length=100, blank=True)
class PlanQuota(models.Model):
plan = models.ForeignKey('Plan', on_delete=models.CASCADE)
quota = models.ForeignKey('Quota', on_delete=models.CASCADE)
value = models.IntegerField(default=1, null=True, blank=True)
I have to get all quota and their value from PlanQuota in the plan serializer while getting a list of plans.
I have the following serializer
class PlanQuotaSerialier(serializers.ModelSerializer):
class Meta:
model = PlanQuota
depth = 1
fields = ['quota', 'value']
class PlanListSerializer(serializers.ModelSerializer):
plan_quota = PlanQuotaSerialier(read_only=True, many=True)
class Meta:
model = Plan
depth = 1
fields = ['name', 'default', 'created', 'plan_quota']
But there is no plan_quota in the response.
How can I add all Quota and their value for each plan in a single query (SQL JOIN)?
Edit 2:
Adding source to the serializer field worked
plan_quota = PlanQuotaSerialier(source='planquota_set', many=True)
And the result is like
"results": [
{
"name": "Test Plan 1",
"default": true,
"plan_quotas": [
{
"quota": {
"id": 1,
"order": 0,
"codename": "TEST",
"name": "Test Domain",
"unit": "count",
"description": "",
"is_boolean": false,
"url": ""
},
"value": 10
},
]
}
]
Can I club all fields from quota with value field in the plan_quotas list?
class PlanQuota(models.Model):
plan = models.ForeignKey('Plan', on_delete=models.CASCADE, related_name='plan_quotas')
quota = models.ForeignKey('Quota', on_delete=models.CASCADE)
value = models.IntegerField(default=1, null=True, blank=True)
class PlanListSerializer(serializers.ModelSerializer):
class Meta:
model = Plan
depth = 1
fields = ['name', 'default', 'created', 'plan_quotas']
This is how I got it solved.
For the first query, added source
plan_quota = PlanQuotaSerialier(source='planquota_set', many=True)
For removing quota key, added to_presentation() in the PlanQuotaSerializer
def to_representation(self, instance):
representation = super().to_representation(instance)
if 'quota' in representation:
representation['quota']['quota_id'] = representation['quota'].pop('id')
representation.update(representation.pop('quota'))
return representation

Django Rest Api - ManyToManyField, Display 'title' instead of 'id' in the Exercises Array

Django Rest Api - ManyToManyField, Display 'title' instead of 'id' in the Exercises Array
HTTP 200 OK
Allow: GET, POST, PUT, DELETE, PATCH
Content-Type: application/json
Vary: Accept
[
{
"id": 1,
"title": "Push Workout Bjarred",
"description": "Kör Hårt!",
"exercises": [
3,
4,
5,
6,
9,
10
],
"cardio": [
4
]
},
{
"id": 2,
"title": "Pull Workout Loddekopinge",
"description": "",
"exercises": [
1,
2,
7,
8
],
"cardio": []
},
{
"id": 3,
"title": "CardioPass",
"description": "",
"exercises": [],
"cardio": [
2,
3,
4
]
}
]
Serializer (So I want to display the title and not the id for every exercise)
class WorkoutSerializer(serializers.ModelSerializer):
class Meta:
model = Workout
fields = ('id', 'title', 'description', 'exercises', 'cardio')
Here are the models, note that I want to display the exercise name for every exercise in the api array, I don't want the id! - That is passed right now! :)
class Bodypart(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Exercise(models.Model):
name = models.CharField(max_length=40)
bodyparts = models.ManyToManyField(Bodypart, blank=True)
def __str__(self):
return self.name
class Cardio(models.Model):
name = models.CharField(max_length=40)
time = models.IntegerField(default=10)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = 'cardio'
class Workout(models.Model):
title = models.CharField(max_length=120)
description = models.CharField(max_length=1000, blank=True)
exercises = models.ManyToManyField(Exercise, blank=True)
cardio = models.ManyToManyField(Cardio, blank=True)
def __str__(self):
return self.title
You can get this by changing your WorkoutSerializer like this
class WorkoutSerializer(serializers.ModelSerializer):
exercises = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
fields = ('id', 'title', 'description', 'exercises', 'cardio', )
You will get the result json like this
[
{
"id": 1,
"title": "Push Workout Bjarred",
"description": "Kör Hårt!",
"exercises": [
"Exercise 1",
"Exercise 2",
"Exercise 4",
"Exercise 3"
],
"cardio": [
4
]
},
...
More about django rest serializers see https://www.django-rest-framework.org/api-guide/relations/
Create a serializer for the Exercise model with the name field:
class ExerciseSerializer(serializers.ModelSerializer):
class Meta:
model = Exercise
fields = ('name', )
And add it to the Workout serializer:
class WorkoutSerializer(serializers.ModelSerializer):
exercises = ExerciseSerializer(many=True, read_only=True)
class Meta:
fields = ('id', 'title', 'description', 'exercises', 'cardio', )
Try adding exercises field in serializer as below
exercises = serializers.SlugRelatedField(read_only=True, slug_field='title')
I had the similar issue but and this is how I solved it:
class WorkoutSerializer(serializers.ModelSerializer):
exercises = serializers.StringRelatedField(many=True, read_only=True)
class Meta:
fields = ('id', 'title', 'description', 'exercises', 'cardio', )
Updated the Model:
class Workout(models.Model):
title = models.CharField(max_length=120)
description = models.CharField(max_length=1000, blank=True)
exercises = models.ManyToManyField(Exercise, **related_name='exercises'**, blank=True)
cardio = models.ManyToManyField(Cardio, blank=True)
def __str__(self):
return self.title
I came across the same problem and now I know the answer:
class WorkoutSerializer(serializers.ModelSerializer):
exercise = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
cardio = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)
class Meta:
fields = ('id', 'title', 'description', 'exercise', 'cardio', )
It must be exercise and cardio which are the exact fields in workout (rather than exercises and cardios. So basically, the answer from Mwangi Kabiru above is correct except that slug_field must be "name" and not title because the fields in class Exercise and Cardio are name, not title.

How can I implement a nested representation using intermediate model?

I am quite new to Django and Django Rest Framework.
What I like to do in my project is display intermediate model's information using ListAPIView and also include detailed information about another Model connected to the intermediate model with a Foreign Key Relationship in the form of nested representation.
I have 3 models in my project, User, Content, Bookmark.
My model goes like below.
class MyUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
username = models.CharField(max_length=50)
joined_date = models.DateTimeField(auto_now_add=True)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
facebook_id = models.CharField(max_length=50, blank=True)
is_facebook = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
class Content(models.Model):
seq = models.CharField(max_length=20, unique=True)
title = models.CharField(max_length=100, null=True)
start_date = models.DateField(null=True)
end_date = models.DateField(null=True)
place = models.TextField(null=True)
realm_name = models.TextField(null=True)
area = models.TextField(null=True)
price = models.TextField(null=True)
content = models.TextField(null=True)
ticket_url = models.TextField(null=True)
phone = models.TextField(null=True)
thumbnail = models.TextField(null=True)
bookmarks = models.ManyToManyField(User, through='Bookmark')
The last model, an intermediate model to connect MyUser and Content.
class Bookmark(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
content = models.ForeignKey(Content, on_delete=models.CASCADE)
created_date = models.DateTimeField(auto_now_add=True)
description = models.CharField(max_length=200, null=True)
class Meta:
unique_together = (('user', 'content'),)
ordering = ['-created_date']
def __str__(self):
return '{} bookmarked by user {}'.format(self.content, self.user)
I want to use BookmarkListAPIView to show certain user's bookmark information and some detailed information of Contents that the user added such as title, price, and start_date.
Below is my Serializer and ListAPIView.
class BookmarkListView(generics.ListAPIView):
queryset = Bookmark.objects.all()
serializer_class = BookmarkSerializer
pagination_class = DefaultResultsSetPagination
def get_queryset(self):
user = self.request.user
return user.bookmark_set.all().order_by('-created_date')
class BookmarkedContentSerializer(serializers.ModelSerializer):
class Meta:
model = Content
fields = ('title', 'start_date', 'price')
class BookmarkSerializer(serializers.ModelSerializer):
content = BookmarkedContentSerializer(many=True, read_only=True)
#bookmark = BookmarkedContentSerializer(many=True, read_only=True)
class Meta:
model = Bookmark
fields = ('content', 'user')
Currently, the API gives me results just like below.
{
"count": 6,
"next": null,
"previous": null,
"results": [
{
"content": 8,
"user": 4
},
{
"content": 6,
"user": 4
},
{
"content": 1,
"user": 4
},
{
"content": 2,
"user": 4
},
{
"content": 3,
"user": 4
},
{
"content": 10,
"user": 4
}
]
}
As mentioned above, I want to use the content ID to fetch more detailed information about each bookmark instance.
It will look like
{
"count": 6,
"next": null,
"previous": null,
"results": [
{ "content_id": 4
"title": A,
"price": 10$,
"user": 4
},
{
"content_id": 6,
"title": B,
"price": 13$,
"user": 4
},
{
"content_id": 1,
"title": C,
"price": 4$,
"user": 4
},
]
}
I tried many things written in the DRF doc but was not able to find any materials related to my situation.
If you have any idea, please help me.
I think the key you're missing is the source keyword argument for each field. Here's some docs on it for you: http://www.django-rest-framework.org/api-guide/fields/#source
And I believe this implementation should work for you or at least be very close:
class BookmarkSerializer(serializers.ModelSerializer):
content_id = serializers.IntegerField(source='content.id')
title = serializers.CharField(source='content.title')
price = serializers.CharField(source='content.price')
user = serializers.IntegerField(source='user.id')
class Meta:
model = Bookmark
fields = ('content_id', 'user', title', 'price')

How to perform nested serialization in Django?

I am new to Django.I am using Django REST Framework and I need to get json from serializers in the following format:
{
"result" : 200,
"categories" : [{
"id" : "V001",
"name": "Vehicles",
"description": "All types of motor and non-motor vehicles",
"icon": "http://images.maa.ae/static/icons/vehicles.png",
"subcategories": [{
"id" : "V00101",
"name": "Cars",
"description": "Motor Cars",
"subcategories": [{
"id" : "V0010101",
"name": "Passenger Cars",
"description": "All types of passenger cars"
}]
},
{
"id" : "V00102",
"name": "Bikes",
"description": "Bikes",
"subcategories": [{
"id" : "V0010201",
"name": "Motor Bikes",
"description": "All kinds of motor bikes"
},
{
"id" : "V0010202",
"name": "Sports Bikes",
"description": "All kinds of sports bikes"
}]
}]
}]
}
My Model class is:
class Category(models.Model):
_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=128)
description = models.CharField(max_length=150,null=True, blank=True)
icon = models.ImageField(upload_to='icons',null=True, blank=True)
parent_id = models.ForeignKey('self', null=True, blank=True)
and my serializers class is:
class CategorySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Category
fields = ('_id', 'name', 'description', 'icon')
From above implementation I can get Array of Category objects in JSON format.
But I don't know how can I include 'subcategories' in my serializer class.Please help me to get an output similar to json shown in above format.
Use Django Rest Framework.
http://www.django-rest-framework.org/
Keep your code DRY
from rest_framework import serializers
class CategoriesSerializer(serializers.ModelSerializer):
class Meta:
model = Categories
Views
from rest_framework import viewsets
from .serializers import CategoriesSerializer
class CategoryViewSet(viewsets.ModelViewSet):
queryset = Categories.objects.all()
serializer_class = CategoriesSerializer
Example
models.py:
class Sprints(models.Model):
name = models.CharField(default='', blank=True, max_length=90)
description = models.TextField(default='')
class Tasks(models.Model):
date_posted = models.DateTimeField(auto_now_add=True)
name = models.CharField(default='', blank=True, max_length=90)
end = models.DateTimeField(null=True, blank=True)
sprint = models.ForeignKey(Sprints, related_name='tasks')
class Meta:
ordering = ['-date_posted']
unique_together = ['name', 'sprint']
serializers.py:
class TaskSerializer(serializers.ModelSerializer):
class Meta:
model = Tasks
fields = ('id', 'name', 'date_posted', 'end')
class SprintSerializer(serializers.ModelSerializer):
tasks = TaskSerializer(many=True, read_only=True)
class Meta:
model = Sprints
fields = ('id', 'name', 'description', 'tasks')
views.py
class SprintsViews(viewset.ModelViewSet):
queryset = Sprints.objects.all()
serializer_class = SprintSerializer
You can also add a slug field to the models to make them easier to deal with, but this is how for now :)
You should consider having a look at THIS. The usage of depth meta attribute enables you to retrieve related objects to the depth you set.
By doing so, it automatically retrieves nested data.
It is very convenient to avoid using serializers in both sides and thus having ImportError caused by cycles too.
Use related_name and on_delete on your parent_id field in models.py file
class Category(models.Model):
_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=128)
description = models.CharField(max_length=150,null=True, blank=True)
icon = models.ImageField(upload_to='icons',null=True, blank=True)
parent_id = models.ForeignKey('self', null=True, blank=True, related_name='children', on_delete=models.CASCADE)
Now add the packege pip install djangorestframework-recursive on your project
serializers.py:
##################################
from rest_framework_recursive.fields import RecursiveField
##################################
class CategorySerializer(serializers.ModelSerializer):
children = RecursiveField(many=True)
class Meta:
model = Category
fields = ('_id', 'name', 'description', 'icon', 'parent_id', 'children')
views.py (Serializers view file):
class CategoryListAPIView(generics.ListAPIView):
serializer_class = CategorySerializer
queryset = Category.objects.filter(parent_id__isnull=True)
urls.py:
############################
############################
urlpatterns = [
path('category/', views.CategoryListAPIView.as_view(), name="categoryAPI"),
######################################
]