Correct way to get/create nested objects with DRF? - python

I have the following models:
class Platform(models.Model): # Get
key = models.CharField(max_length=32, unique=True)
class PlatformVersion(models.Model): # Get or create
platform = models.ForeignKey(Platform, on_delete=models.CASCADE)
major = models.IntegerField(db_index=True)
minor = models.IntegerField(db_index=True)
build = models.IntegerField(db_index=True)
class DeviceManufacturer(models.Model): # Get or create
name = models.CharField(max_length=64, unique=True)
class DeviceModel(models.Model): # Get or create
manufacturer = models.ForeignKey(DeviceManufacturer, on_delete=models.CASCADE)
platform = models.ForeignKey(Platform, on_delete=models.CASCADE)
# ...
# And many others
POST JSON looks like this:
{
"device_model": {
"manufacturer": {
"name": "samsung"
},
"platform": {
"key": "android"
}
},
"version": {
"platform": {
"key": "android"
},
"major": 8,
"minor": 1,
"build": 0
}
}
I have the following serializers:
class PlatformSerializer(serializers.ModelSerializer):
class Meta:
model = Platform
fields = ('name', 'key',)
extra_kwargs = {
'name': {
'read_only': True,
},
}
def create(self, validated_data):
return Platform.objects.get(key=validated_data['key'])
class PlatformVersionSerializer(serializers.ModelSerializer):
platform = PlatformSerializer()
class Meta:
model = PlatformVersion
fields = ('platform', 'major', 'minor', 'build',)
def create(self, validated_data):
# ?
pass
class DeviceManufacturerSerializer(serializers.ModelSerializer):
class Meta:
model = DeviceManufacturer
fields = ('name',)
def create(self, validated_data):
manufacturer, _ = DeviceManufacturer.objects.get_or_create(
defaults={
'name': validated_data['name'],
},
name=validated_data['name']
)
return manufacturer
class DeviceModelSerializer(serializers.ModelSerializer):
manufacturer = DeviceManufacturerSerializer()
platform = PlatformSerializer()
class Meta:
model = DeviceModel
fields = ('manufacturer', 'platform')
def create(self, validated_data):
# ?
pass
I've marked in serializers places where I don't know how to correctly do the object creation, as some of nested objects are «read-only» (I mean that I in fact use get, not create), some of them are get or create.
How should I do it?

Related

Different representations of an object in the DRF [duplicate]

This question already has answers here:
DRF: Simple foreign key assignment with nested serializers?
(12 answers)
Closed 6 months ago.
I have a model:
class Schedule(LogSaveDeleteMixin, models.Model):
name = models.CharField(max_length=40)
start_date = models.DateTimeField(null=True, blank=True)
class DeliveryChannel(LogSaveDeleteMixin, models.Model):
name = models.CharField(unique=True, max_length=40)
state = models.CharField(choices=DeliveryChannelState.choices, default='draft', max_length=15)
schedule = models.ForeignKey(Schedule, null=True, on_delete=models.SET_NULL)
And standard ModelSerializer:
class DeliveryChannelsSerializer(serializers.ModelSerializer):
class Meta:
model = DeliveryChannel
fields = '__all__'
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = '__all__'
For read requests (GET) I want to receive schedule field as a nested serializer:
{
"id": 0,
"name": "string",
"state": "archived",
"schedule": {
"id": 0,
"name": "string",
"start_date": "2022-08-12T02:41:32.187Z",
}
}
But for writing (POST, PUT) I want to get only schedule id:
{
"id": 0,
"name": "string",
"state": "archived",
"schedule": 0
}
I would like to know the best practices for do this
You can just set some fields in the DeliveryChannelsSerializer.
class DeliveryChannelsSerializer(serializers.ModelSerializer):
schedule = ScheduleSerializer(read_only = True)
schedule_id = serializers.IntegerField(write_only = True)
class Meta:
model = DeliveryChannel
fields = '__all__'
extra_fields = ['schedule_id']
The POST payload should be like the following:
{
"name": "string",
"state": "archived",
"schedule_id": 1
}
You can make two different serializers for DeliveryChannels.
class ScheduleSerializer(serializers.ModelSerializer):
class Meta:
model = Schedule
fields = ['id', 'name', 'start_date']
class DeliveryChannelSerializer(serializers.ModelSerializer):
# Serializer for POST,PUT
class Meta:
model = DeliveryChannel
fields = ['id', 'name', 'state', 'schedule']
class DeliveryChannelDetailSerializer(serializers.ModelSerializer):
# Serializer for GET
schedule = ScheduleSerializer()
class Meta:
model = DeliveryChannel
fields = ['id', 'name', 'state', 'schedule']
if you use ModelViewSet you need to override get_serializer_clas
def get_serializer_class(self):
if self.action == "list":
return DeliveryChannelDetailSerializer
return DeliveryChannelSerializer

How to set nested related fields serializers in Djando Rest Framework?

I'm using Django Rest Framework to build an API where I have the following models of Users making, confirming and showing interest on Events:
models.py
class User(AbstractBaseUser, PermissionsMixin):
user_name = models.CharField(_("user name"), max_length=150, unique=True)
slug = AutoSlugField(populate_from='user_name', unique=True)
class Event(models.Model):
name = models.CharField(max_length=100, blank=False, null=False)
owner = models.ForeignKey(User, related_name="owned_events", on_delete=models.SET_NULL, blank=True, null=True)
confirmed = models.ManyToManyField(User, related_name="confirmed_events", blank=True)
interested = models.ManyToManyField(User, related_name="interested_events", blank=True)
to serialize it I used the following code as I found here and at the DRF docs:
serializers.py
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = User
fields = [
"url",
"user_name",
"password",
]
extra_kwargs = { "password": {"write_only": True} }
class EventSerializer(serializers.HyperlinkedModelSerializer):
owner = UserSerializer(required=False)
confirmed = UserSerializer(required=False, many=True)
interested = UserSerializer(required=False, many=True)
class Meta:
model = Event
lookup_field = 'slug'
extra_kwargs = { 'url': {'lookup_field': 'slug'} }
fields = [
"url",
"owner",
"name",
"confirmed",
"interested",
]
It works just fine like that, but I wanted the UserSerializer to show confirmed and interested events of each user just like each event shows the users confirmed and interested. I changed serializers and got the url of each event, like that:
serializers.py with HyperlinkedRelatedField on UserSerializer
class UserSerializer(serializers.HyperlinkedModelSerializer):
confirmed_events = serializers.HyperlinkedRelatedField(
queryset=Event.objects.all(),
view_name='event-detail',
lookup_field='slug',
many=True,
required=False
)
interested_events = serializers.HyperlinkedRelatedField(
queryset=Event.objects.all(),
view_name='event-detail',
lookup_field='slug',
many=True,
required=False
)
class Meta:
model = User
fields = [
"url",
"user_name",
"password",
"confirmed_events",
"interested_events",
]
extra_kwargs = { "password": {"write_only": True} }
This got me the following JSON from the User model:
{
"user_name": "d",
"confirmed_events": [],
"interested_events": [
"http://localhost:8000/events/eqwer-2/",
"http://localhost:8000/events/test/",
"http://localhost:8000/events/test-2/",
"http://localhost:8000/events/test-3/",
]
}
And from the Event model:
{
"url": "http://localhost:8000/events/eqwer-2/",
"owner": null,
"name": "eqwer",
"slug": "eqwer-2",
"confirmed": [],
"interested": [
{
"user_name": "d",
"confirmed_events": [],
"interested_events": [
"http://localhost:8000/events/eqwer-2/",
"http://localhost:8000/events/test/",
"http://localhost:8000/events/test-2/",
"http://localhost:8000/events/test-3/",
]
}
]
},
The Event model JSON is fine because it shows each user's data, but I wanted the User JSON to show each event data instead of just the event URL, it'd be something like:
{
"user_name": "d",
"confirmed_events": [],
"interested_events": [
{
"url": "http://localhost:8000/events/eqwer-2/",
"owner": null,
"name": "eqwer",
"slug": "eqwer-2",
},
]
}
Create a separate serializer for interested_events like so:
class InterestedEventsSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
fields = ('url', 'owner', 'name', 'slug')
And in your UserSerializer declare the interested_events using the serializer above:
class UserSerializer(serializers.HyperlinkedModelSerializer):
confirmed_events = ... #
interested_events = InterestedEventsSerializer(many=True)
class Meta:
model = User
fields = [
"url",
"user_name",
"password",
"confirmed_events",
"interested_events",
]
extra_kwargs = { "password": {"write_only": True} }

How to update nested serializers with file field

I have 2 models: Profile and Image. Profile field "logo" related to Image with models.ForeignKey() construction. I want to update my Profile record with update request (Patch with JSON payload).
How can I do that?
I've tried to send this JSON
{
"name": "TestName",
"company": "myCompany",
"phone": "33222111",
"website": "site.com"
}
And it's OK, record have updated. But! In Image model I have models.ImageField(). How I should deal with this field through another serializer?
Then I've tried to send this JSON (122 id of existed Image record in DB)
REQUEST:
{
"logo": 122
}
ANSWER:
{
"logo": {
"non_field_errors": [
"Invalid data. Expected a dictionary, but got int."
]
}
}
OK, so, think I should send object of exist record
REQUEST:
{
"logo": {
"id": 122,
"uuid": "bf9ba033-208f-47e0-86e5-93c44e05a616",
"created": "2018-12-20T12:54:57.178910Z",
"original_name": "hello.png",
"filetype": "png",
"file": "http://localhost/upload/img/0a9lg1apnebb.png",
"owner": 1
}
}
ANSWER:
{
"logo": {
"file": [
"The submitted data was not a file. Check the encoding type on the form."
]
}
}
Here my two models and serializers
class Image(models.Model):
id = models.AutoField(primary_key=True)
uuid = models.UUIDField(primary_key=False, default=uuid.uuid4, editable=False)
created = models.DateTimeField(auto_now_add = True)
original_name = models.CharField(max_length = 256, default=None)
filetype = models.CharField(max_length = 10, default=None)
file = models.ImageField(upload_to=update_img_filename,
default="static/noimg.jpg")
owner = models.ForeignKey(User, related_name="images",
on_delete=models.CASCADE, null=True)
class Profile(models.Model):
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
name = models.CharField(max_length=256, blank=True, null=True)
company = models.CharField(max_length=256, blank=True, null=True)
phone = models.CharField(max_length=256, blank=True, null=True)
website = models.CharField(max_length=256, blank=True, null=True)
logo = models.ForeignKey(Image, related_name="profile_logo",
on_delete=models.SET_NULL, null=True,
blank=True, default=None)
is_admin = models.BooleanField(default=False)
owner = models.ForeignKey(User, related_name="profiles",
on_delete=models.CASCADE, null=True)
class ProfileSerializer(serializers.ModelSerializer):
logo = ImageSerializer()
class Meta:
model = Profile
fields = '__all__'
extra_kwargs = {
'owner': {'read_only': True},
'user': {'read_only': True},
'is_admin': {'read_only': True},
}
def create(self, validated_data):
return Profile.objects.create(**validated_data)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
class ImageSerializer(serializers.ModelSerializer):
class Meta:
model = Image
fields = '__all__'
extra_kwargs = {
'owner': {'read_only': True}
}
def validate(self, data):
extension = str(data['file']).split(".")[-1].lower()
original_name = str(data['file'])
if 'original_name' not in data:
data['original_name'] = original_name
if 'filetype' not in data:
data['filetype'] = extension
return data
def create(self, validated_data):
return Image.objects.create(**validated_data)
in database, model Profile will have logo_id mapping with model Image.
Try json like this if you want to have relationship with existed logo:
{
"name": "TestName",
"company": "myCompany",
"phone": "33222111",
"website": "site.com",
"logo: 122
}
Serializer:
class ProfileSerializer(serializers.ModelSerializer):
logo = ImageSerializer()
class Meta:
model = Profile
fields = '__all__'
extra_kwargs = {
'owner': {'read_only': True},
'user': {'read_only': True},
'is_admin': {'read_only': True},
}
def create(self, validated_data):
logo_id = validated_data.pop('logo')
profile = Profile.objects.create(**validated_data)
logo_instance = Image.objects.get(pk=logo_id)
profile.logo = logo_instance
profile.save()
def update(self, instance, validated_data):
nested_serializer = self.fields['logo']
nested_instance = instance.profile
nested_data = validated_data.pop('logo')
nested_serializer.update(nested_instance, nested_data)
return super(ProfileSerializer, self).update(instance, validated_data)
if you need set the image only from the existing images, you can do that:
class ProfileSerializer(serializers.ModelSerializer):
logo = ImageSerializer()
class Meta:
model = Profile
fields = '__all__'
extra_kwargs = {
'owner': {'read_only': True},
'user': {'read_only': True},
'is_admin': {'read_only': True},
}
class ImageSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
return Image.objects.get(pk=data)
class Meta:
model = Image
fields = '__all__'
extra_kwargs = {
'owner': {'read_only': True}
}
...
Then you will can send json:
{
"name": "TestName",
"company": "myCompany",
"phone": "33222111",
"website": "site.com"
"logo": 122
}
And image will be set.

PUT on ModelViewSet with nested object Django Rest Framework

I'm building an API with the django rest framework. I have these models:
class Organism(models.Model):
name = models.CharField(max_length=255)
address = models.ForeignKey(Address, on_delete=models.CASCADE)
type = models.ForeignKey(Type, on_delete=models.CASCADE)
class Address(models.Model):
street = models.CharField(max_length=255, blank=True)
class Type(models.Model):
name = models.CharField(max_length=255, blank=True)
This is the view for my mode Organism :
class OrganismViewSet(viewsets.ModelViewSet):
queryset = Organism.objects.all()
serializer_class = OrganismSerializer
pagination_class = StandardResultsSetPagination
filter_backends = (filters.SearchFilter, DjangoFilterBackend)
filter_class = OrganismFilter
search_fields = ('name')
And my serializer:
class OrganismSerializer(serializers.ModelSerializer):
addresse = AddressSerializer()
type = TypeSerializer()
class Meta:
model = Organism
fields = '__all__'
def update(self, instance, validated_data):
// What I should write to do something "elegant"
Let's imagine when I get my Organism, I have:
{
"address": {
"id": 1
"street": "test"
},
type: {
"id": 1,
"name": "type Organism"
},
"name":"TestTest",
}
So I'm trying to update an Organism (I want to change the name of the street but not create a new object AND change the Type which exists in my database) by sending this:
{
"address": {
"id": 1
"street": "new name"
},
type: {
"id": 2,
"name": "new type"
}
"name":"TestTest",
}
And the fact is I don't have the ID of my object in the parameter "validated_data" of the method update.
If you guys know how to proceed... Thank you in advance.

Django Rest Framework foreign key nesting

I am trying to nest my Users table inside my Relationships table. So instead of this:
[
{
"user": 1,
"related_user": 2,
"relationship": "followed_by"
}
]
I am trying to get this:
[
{
"user": {
"username": "user1",
"name": "User 1",
"email": "bla",
"phone": "bla",
"date_joined": "2017-11-01T21:34:13.101256Z"
},
"related_user": {
"username": "user2",
"name": "User 2",
"email": "bla",
"phone": "bla",
"date_joined": "2017-11-01T21:34:13.101256Z"
},
"relationship": "followed_by"
}
]
I looked up tutorials and I tried adding serializers.RelatedField , UserSerializer(many=true, read-only=true) etc. but nothing worked
Models.py
class User(models.Model):
username = models.CharField(max_length=255)
name = models.CharField(max_length=255)
email = models.CharField(max_length=255)
phone = models.CharField(max_length=255)
date_joined = models.DateTimeField(auto_now_add=True, blank=True)
def __str__(self):
return str(self.pk) + ", " + self.username
RELATIONSHIP_CHOICE = [
("follows", "follows"),
("followed_by", "followed_by"),
("none", "none"),
]
class Relationship(models.Model):
user = models.ForeignKey(User, related_name="primary_user", null=True)
related_user = models.ForeignKey(User, related_name="related_user", null=True)
relationship = models.CharField(max_length=40, choices=RELATIONSHIP_CHOICE, default=RELATIONSHIP_CHOICE[0])
Serializers.py
from rest_framework import serializers
from . import models
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = models.User
fields = (
'username',
'name',
'email',
'phone',
'date_joined',
)
class RelationshipSerializer(serializers.ModelSerializer):
related_user = UserSerializer(many=True)
class Meta:
model = models.Relationship
fields = (
'user',
'related_user',
'relationship',
'related_user'
)
I tried to add related user to my serializer but it didnt work. I am getting an error: 'User' object is not iterable
Any help is appreciated.
class RelationshipSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
related_user = UserSerializer(read_only=True)
class Meta:
model = models.Relationship
fields = (
'user',
'related_user',
'relationship'
)
user = UserSerializer(read_only=True, many=True) is for manytomany field,user = UserSerializer(read_only=True) is for ForeignKey field.

Categories