I am using Django Rest Framework for a project and I am running into a problem. When the frontend creates a Team they want to reference all relationships with an ID, but when getting the Team, they want the data from the relationship. How can I achieve this?
models:
class Team(models.Model):
class Meta:
db_table = "team"
team_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
organization = models.ForeignKey(Organization, on_delete=models.CASCADE)
class Organization(models.Model):
class Meta:
db_table = "organization"
organization_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
class Position(models.Model):
class Meta:
db_table = "position"
position_id = models.AutoField(primary_key=True)
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name="positions")
class Player(model.Model):
class Meta:
db_table = "player"
player_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
positions = models.ManyToManyField(Position, related_name="players")
serializers:
class TeamSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ["team_id", "name", "organization", "positions"]
positions = PositionSerializer(many=True) # This is merely for output. There is no need to create a position when a team is created.
organization = OrganizationSerializer() # Since an organization already exists I'd like to just give an organization_id when creating/editing a team.
# I don't think the other serializers matter here but can add them on request.
So when doing POST or PATCH on a team, I'd like the front end to be able to pass this payload
{
"name": "My Team",
"organization": 1
}
but when doing a GET on a team, I'd like the front end to receive this response.
{
"team_id": 1,
"name": "My Team",
"organization": {
"organization_id": 1,
"name": "My Organization"
},
"positions": [{
"position_id": 1,
"players": [{
"player_id": 1,
"name": "Member 1"
}
]
}
Is there a a way to achieve this?
In such situations define two serializers, one is for read operations and one is for write operations.
class TeamWriteSerializer(serializers.ModelSerializer):
# see, here no nested relationships...
class Meta:
model = Team
fields = ["name", "organization"]
class TeamReadSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ["team_id", "name", "organization", "positions"]
positions = PositionSerializer(many=True)
organization = OrganizationSerializer()
and now, use these two serializers properly in your views. For example, I hope you are using the ModelViewSet in views,
class TeamModelViewSet(viewsets.ModelViewSet):
def get_serializer_class(self):
if self.request.method.lower() == 'get':
return TeamReadSerializer
else:
return TeamWriteSerializer
Related
Good day,
I'm trying to do a PUT request that contains a nested-object and I can't get the province to update correctly. There didn't seem to be anything obvious in the django-rest-framework docs to help with this and I've investigated the solutions of a few other similar problems but none have helped (set many=false, change to ModelSerializer, specialized serializers, etc.).
Everything else about the address will update correctly and return a 200 response (no errors in django logs either). Am I wrong to assume that django-rest-framework handles this all for me for free? Do I have to override the update and create methods within the serializer to validate and save the nested-object?
I'm thinking it's because I have the province serializer set to read_only within the address serializer. However, if I remove the read_only modifier on the province serializer, it gives an error about the province already existing:
{
"province": {
"province": [
"valid province with this province already exists."
]
}
}
Which is behaviour I do not expect and don't know how to resolve. I am not trying to add or update the province. I just want to change the province code in the address.province field and I can't use a string "MB" because it expects an object. I effectively want this behaviour:
UPDATE agent_business_address
SET province = 'MB'
WHERE agent_id = 12345;
-- agent_business_address.province has a foreign key constraint on valid_province.province
-- valid_province is populated with all the 2-letter abbreviations for provinces(
I make this PUT request to /api/agent-business-address/
{
"address": "123 Fake St",
"agent_id": 12345,
"city": "Calgary",
"dlc": "2021-10-11 14:03:03",
"operator_id": 4,
"postal_code": "A1B 2C3",
"province": {
"description": "Manitoba",
"province": "MB"
},
"valid_address": "N"
}
That is received by this ViewSet:
class AgentBusinessAddressViewSet(viewsets.ModelViewSet):
queryset = AgentBusinessAddress.objects.all()
serializer_class = AgentBusinessAddressSerializer
Relevant serializers:
class AgentBusinessAddressSerializer(serializers.HyperlinkedModelSerializer):
province = ValidProvinceSerializer(read_only=True) # Removing the read_only causes the error above.
class Meta:
model = AgentBusinessAddress
fields = ('agent_id', 'operator_id', 'dlc', 'address', 'city', 'province', 'postal_code', 'valid_address')
class ValidProvinceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ValidProvince
read_only_fields = ('operator_id', 'dlc')
fields = ('province', 'description')
Relevant models:
class AgentBusinessAddress(models.Model):
agent = models.OneToOneField(Agent, models.DO_NOTHING, primary_key=True)
operator_id = models.SmallIntegerField()
dlc = models.DateTimeField()
address = models.CharField(max_length=100)
city = models.CharField(max_length=80)
province = models.ForeignKey('ValidProvince', models.DO_NOTHING, db_column='province')
postal_code = models.CharField(max_length=7)
valid_address = models.CharField(max_length=1)
class Meta:
managed = False
db_table = 'agent_business_address'
class ValidProvince(models.Model):
province = models.CharField(primary_key=True, max_length=2)
operator_id = models.SmallIntegerField()
dlc = models.DateTimeField()
description = models.CharField(max_length=30, blank=True, null=True)
class Meta:
managed = False
db_table = 'valid_province'
Any help would be appreciated.
Solved it with the help of the specialized serializers post after a few re-reads.
I updated the serializers to this:
# This gets called for non-GET requests.
class AgentBusinessAddressSerializer(serializers.ModelSerializer):
class Meta:
model = AgentBusinessAddress
fields = ('__all__')
# This get called for GET requests.
class AgentBusinessAddressReadSerializer(AgentBusinessAddressSerializer):
province = ValidProvinceSerializer(read_only=True)
class Meta:
model = AgentBusinessAddress
fields = ('__all__')
I updated the viewset to this:
class AgentBusinessAddressViewSet(viewsets.ModelViewSet):
queryset = AgentBusinessAddress.objects.all()
def get_serializer_class(self):
if self.request.method in ['GET']:
return AgentBusinessAddressReadSerializer
return AgentBusinessAddressSerializer
Now I just send the primary key for the province in the PUT request:
{
"address": "123 Fake St",
"agent": 10000003,
"city": "Calgary",
"dlc": "2021-10-11 19:47:38",
"operator_id": 4,
"postal_code": "A1B 2C3",
"province": "NS",
"valid_address": "N"
}
I get a PUT 200 response back and verify in the DB that the province is now 'NS'.
{
"agent": 10000003,
"operator_id": 4,
"dlc": "2021-10-11T19:47:38",
"address": "123 Fake St",
"city": "Calgary",
"postal_code": "A1B 2C3",
"valid_address": "N",
"province": "NS",
}
I have 2 models connected via M2M model:
class Person(models.Model):
name = models.CharField(max_length=30)
class Group(models.Model):
name = models.CharField(max_length=100)
members = models.ManyToManyField(Person, through='GroupPerson', related_name='groups')
class GroupPerson(models.Model):
group = models.ForeignKey(Group, on_delete=models.CASCADE)
person = models.ForeignKey(Person, on_delete=models.CASCADE)
rank = models.CharField(max_length=100', default='New')
and serializers
class GroupPersonSerializer(serializers.ModelSerializer):
class Meta:
model = GroupPerson
fields = '__all__'
class GroupSerializer(serializers.ModelSerializer):
members = serializers.PrimaryKeyRelatedField(many=True, queryset=Person.objects.all())
class Meta:
model = Group
fields = '__all__'
class PersonSerializer(serializers.ModelSerializer):
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=Group.objects.all())
class Meta:
model = Person
fields = '__all__'
And API go get a group returns
[
{
"name": "...",
"members": [ 1, ... ]
}
]
How do I get a response something like following:
[
{
"name": "...",
"members": [
{
"group_id": 1,
"person_id": 1,
"rank": "New"
},
...
]
}
]
I.e., I want to GET/POST/PATCH all fields of the through relation
Try:
class PersonSerializer(serializers.ModelSerializer):
groups = serializers.SerializerMethodField()
def get_groups(self, person):
queryset = GroupPerson.objects.filter(person=person)
return GroupPersonSerializer(queryset, many=True).data
class Meta:
model = Person
fields = ["id", "name", "groups"]
Instead of using
groups = serializers.PrimaryKeyRelatedField(many=True, queryset=Group.objects.all())
Do
groups = GroupSerializer(many=True, read_only=True)
This will allow DRF to serialize groups as a list of nested objects. For more details, check out the documentation for Nested Relationships
I am having trouble in showing cross relation data in my api end-point
this is my serlization class:
class ArticleSerializer(serializers.ModelSerializer):
# these two variables are assigned coz, author and category are foregin key
author = serializers.StringRelatedField()
category = serializers.StringRelatedField()
class Meta:
model = Article
fields = '__all__'
Above serialization working fine but not satisfy needs.
And this is my models
from django.db import models
import uuid
class Organization(models.Model):
organization_name = models.CharField(max_length=50)
contact = models.CharField(max_length=12, unique=True)
def __str__(self):
return self.organization_name
class Author(models.Model):
name = models.CharField(max_length=40)
detail = models.TextField()
organization = models.ForeignKey(Organization, on_delete=models.DO_NOTHING)
def __str__(self):
return self.name
class Category(models.Model):
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Article(models.Model):
alias = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name='author')
title = models.CharField(max_length=200)
body = models.TextField()
category = models.ForeignKey(Category, on_delete=models.CASCADE)
this is my views:
class ArticleSingle(RetrieveAPIView):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
lookup_field = 'alias'
and currently my end-point showing like these but i dont like it:
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"alias": "029a4b50-2e9a-4d35-afc6-f354d2169c05",
"author": "jhon doe",
"category": "sic-fi",
"title": "title goes here",
"body": "this is body"
}
I want the end-point should show the data with author details like, author, organization_name, contact, etc if you dont understand it, please see my models how i designe it.
The end-point should show each and every related data that with foreign key, i hope you got my issue, thanks for your time
What you are looking for is nested serialization.
Rather than specifying author and category as StringRelatedFields, you should assign them as serializer classes (the same applies to the Organization model):
class OrganizationSerializer(serializers.ModelSerializer):
...
class Meta:
model = Organization
fields = '__all__'
class AuthorSerializer(serializers.ModelSerializer):
organization = OrganizationSerializer()
...
class Meta:
model = Author
fields = '__all__'
class CategorySerializer(serializers.ModelSerializer):
...
class Meta:
model = Category
fields = '__all__'
class ArticleSerializer(serializers.ModelSerializer):
author = AuthorSerializer()
category = CategorySerializer()
class Meta:
model = Article
fields = '__all__'
This mechanism will serialize your data as you desire:
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
{
"alias": "029a4b50-2e9a-4d35-afc6-f354d2169c05",
"author": {
"name": "jhon doe",
"organization": {
"organization_name": "..."
}
...
}
"category": {
"name": "sic-fi"
}
"title": "title goes here",
"body": "this is body"
}
Note: If you want to perform POST requests for creation of those resources, you can find some useful information here.
I wish to add another class to an existing serializer. I am able to do simple serializers, but this one is rather tricky because the second class I wish to add comes after the serializer's class in the models.py- Anybody with a clever solution?
The models.py is as follows:
class Pokemon(models.Model):
id = models.AutoField(primary_key=True)
type = models.OneToOneField(Type, on_delete=models.PROTECT)
stat = models.ForeignKey(Stat, on_delete=models.PROTECT, null=True)
class Skills(models.Model):
id = models.AutoField(primary_key=True)
pokemon = models.ForeignKey(Pokemon, on_delete=models.PROTECT)
owner = models.ForeignKey(Owner, on_delete=models.PROTECT, related_name='name')
and the serializers.py:
class PokemonSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Pokemon
fields = ('id', 'type', 'stat')
The display that I currently have is:
{
"id": 2228,
"type": "http://localhost:8000/type/4628/",
"stat": "99",
}
and the display that I am shooting for is:
{
"id": 2228,
"type": "http://localhost:8000/type/4628/",
"stat": "99",
"owner": "bob doe"
}
Thank you very much in advance!
You should add new serializer called owner for example
class Owner(serializers.ModelSerializer):
class Meta:
model = skills
fields = ('owner',)
in PokemonSerializer add field owner
owner = Owner(many=True, read_only=True)
You can read further more here
you can use source in the a CharField
class PokemonSerializer(serializers.HyperlinkedModelSerializer):
owner = CharField(source="owner.name")
class Meta:
model = Pokemon
fields = ('id', 'type', 'stat', 'owner')
My model looks like this:
class User(TimestampedModel):
name = models.CharField(max_length=30, null=False, blank=False)
device = models.CharField(max_length=255, null=False, blank=False)
class Comment(TimestampedModel):
user = models.ForeignKey(User, on_delete=models.PROTECT, blank=True, null=True)
contents = models.CharField(max_length=510)
rating = models.IntegerField(blank=False, null=False)
And my serializer looks like this:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('name',)
class CommentListItemSerializer(serializers.ModelSerializer):
user = UserSerializer()
class Meta:
model = Comment
fields = ('user', 'contents', 'rating')
And the view:
class CommentsList(generics.ListAPIView):
serializer_class = CommentListItemSerializer
queryset = Comment.objects.all()
It's almost getting the job done ;). The response I'm getting looks like this:
"results": [
{
"user": {
"name": "Ania"
},
"contents": "Very good",
"rating": 6
},
{
"user": {
"name": "Anuk"
},
"contents": "Not very good",
"rating": 1
}
]
There are two problems with that response.
I don't want to have this nested object "user.name". I'd like to receive that as a simple string field, for example "username".
Serializer makes a database query (not a join, but a separate query) for each user, to get his/her name. Since that's unacceptable, how to fix that?
Serializer makes a database query (not a join, but a separate query)
for each user, to get his/her name.
You can use select_related() on the queryset attribute of your view. Then accessing user.name will not result in further database queries.
class CommentsList(generics.ListAPIView):
serializer_class = CommentListItemSerializer
queryset = Comment.objects.all().select_related('user') # use select_related
I don't want to have this nested object "user.name". I'd like to
receive that as a simple string field, for example "username"
You can define a read-only username field in your serializer with source argument. This will return a username field in response.
class CommentListItemSerializer(serializers.ModelSerializer):
# define read-only username field
username = serializers.CharField(source='user.name', read_only=True)
class Meta:
model = Comment
fields = ('username', 'contents', 'rating')
You can add custom functions as fields
class Comment(models.Model):
user = models.ForeignKey(User, on_delete=models.PROTECT, blank=True, null=True)
contents = models.CharField(max_length=510)
rating = models.IntegerField(blank=False, null=False)
def username(self):
return self.user.name
class CommentListItemSerializer(serializers.ModelSerializer):
class Meta:
model = Comment
fields = ('username', 'contents', 'rating')