Django Rest Framework writable nested field using create() - python

I'm trying to write a create method that will write my nested fields but am finding that the nested object isn't written.
This is the sample I was using:
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = ('username', 'email', 'profile')
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(user=user, **profile_data)
return user
But I'm failing to understand what the user=user refers to.
Here is my code:
class MessagesSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(source='pk', read_only=True)
suggested_songs = SongSerializer()
class Meta:
model = Messages
fields = ('id','owner','url','suggested_songs',)
#fields = ('id','url','suggested_songs',)
def create(self, validated_data):
song_data = validated_data.pop('suggested_songs')
message = Messages.objects.create(**validated_data)
Song.objects.create(**song_data)
return message
class SongSerializer(serializers.HyperlinkedModelSerializer):
#id = serializers.IntegerField(source='pk', read_only=True)
class Meta:
model = Song
fields = ('id','title','artist','album','albumId','num_votes','cleared')
read_only_fields = ('song_id')
class Messages(models.Model):
owner = models.OneToOneField(User, primary_key=True, related_name='user_messages', editable=False) #TODO, change owner to 'To'
#suggested_songs = models.ManyToManyField(Song, related_name='suggested_songs')
suggested_songs = models.ForeignKey(Song, null=True, blank=True)
# If a user is added, this runs.
#receiver(post_save, sender=User)
def create_friend_for_user(sender, instance=None, created=False, **kwargs):
if created:
Messages.objects.get_or_create(owner=instance)
# Same as above, but for deletion
#receiver(pre_delete, sender=User)
def delete_friend_for_user(sender, instance=None, **kwargs):
if instance:
Messages.objects.get(owner=instance).delete()
class Song(models.Model):
"""
A model which holds information about the songs.
"""
#song_id = models.IntegerField(primary_key=True)
title = models.CharField(max_length=150, blank=True, default='')
artist = models.CharField(max_length=150, blank=True, default='')
album = models.CharField(max_length=150, blank=True, default='')
albumId = models.CharField(max_length=150, blank=True, default='')
num_votes = models.IntegerField(default=0, blank=True)
cleared = models.BooleanField(default=False, blank=True)
class Meta:
ordering = ('title',)
#managed=True

I think that the issue might be in the MessageSerializer.create method:
def create(self, validated_data):
# here you are popping the suggested songs
song_data = validated_data.pop('suggested_songs')
# so when you create the message here the foreign key is set to NULL
message = Messages.objects.create(**validated_data)
# and here you create the Song instance correctly but it is not
# associated with the message
Song.objects.create(**song_data)
return message
You need to pass the foreign key to the Messages.create method like in the example you have.
def create(self, validated_data):
song_data = validated_data.pop('suggested_songs')
song = Song.objects.create(**song_data)
# song need to be created first because the foreign key is in
# the Messages model
message = Messages.objects.create(suggested_songs=song, **validated_data)
return message
I hope this helps!

Related

Creating a nested class in django and running a helper method

I've the following User model,
class User(AbstractBaseUser, PermissionsMixin, Base):
username = models.CharField(max_length=255, null=False)
email = models.CharField(max_length=255, null=False, unique=True)
user_type = models.CharField(max_length=255, null=False)
is_staff = models.BooleanField(default=False)
is_superuser = models.BooleanField(default=False)
is_active = models.BooleanField(default=False)
And this is my Meeting model
class Meeting(Base):
meeting_code = models.CharField(max_length=255)
owner = models.ForeignKey(User, related_name='meetings', on_delete=models.CASCADE, null=True, blank=True)
members = models.ManyToManyField(User, related_name='meeting_set', null=True, blank=True)
start_time = models.DateTimeField(auto_now_add=True)
end_time = models.DateTimeField(null=True)
When a meeting is created, I want to run this helper function to generate a meeting code
def generate_meetingid():
return ''.join(random.choices(string.ascii_uppercase, k=16))
This is my meeting serializer,
class MeetingSerializer(serializers.ModelSerializer):
owner = UserSerializer(required=True)
class Meta:
model = Meeting
fields = ['id', 'meeting_code', 'owner', 'members', 'start_time', 'end_time', ]
My questions is how do I write the Meeting View set that adds the creating user as the owner and also runs the helper method to create the meeting code.
In essence, I'm trying to complete this view,
class MeetingViewSet(viewsets.ModelViewSet):
queryset = Meeting.objects.all()
serializer_class = MeetingSerializer
def perform_create(self, serializer):
serializer.save()
The easiest way is to override the save method, while I usually prefer using pre_save signal. The logic is similar to generating a unique slug for model instance, the helper function is automatically invoked before you save the object. In your serializer you can simply mark meeting_code as read_only so the meeting_code is not used when creating or updating the instance during deserialization
# override save method
class Meeting(models.Model):
...
def save(self, *args, **kwargs):
# avoid meeting_code IntegrityError
# self._state.adding == True means item was not added in the database yet
if self._state.adding and not self.meeting_code:
self.meeting_code = generate_meetingid()
while Meeting.objects.filter(meeting_code=self.meeting_code).exists():
self.meeting_code = generate_meetingid()
super().save(*args, **kwargs)
# pre_save signal
#receiver(pre_save, sender=Meeting)
def generate_meeting_code(sender, **kwargs):
if kwargs.get('created', False):
# on meeting create
meeting=kwargs.get('instance')
meeting.meeting_code = generate_meetingid()
while Meeting.objects.filter(meeting_code=self.meeting_code).exists():
meeting.meeting_code = generate_meetingid()
Serializer
class MeetingSerializer(serializers.ModelSerializer):
owner = UserSerializer(required=True)
class Meta:
model = Meeting
fields = ['id', 'meeting_code', 'owner', 'members', 'start_time', 'end_time', ]
read_only_fields = ['meeting_code']
Views
class MeetingViewSet(viewsets.ModelViewSet):
queryset = Meeting.objects.all()
serializer_class = MeetingSerializer
def perform_create(self, serializer):
# pass owner to the serializer
serializer.save(owner=self.request.user)

Django Rest - Serializer: must be a instance on create

I'm trying to create create a nested serializer using the Django Rest framework. The relationship is Profile X User but when i use Profile.objects.create(user=profile, **user_data) i get ValueError: Cannot assign "<Profile: Profile object (7)>": "Profile.user" must be a "User" instance..
This should be some rookie misunderstanding of models relationship definitions or the serializer declaration itself but I can't find anything around the docs. If someone can point me a direction I'll be gracefull.
models.py
class User(models.Model):
name = models.CharField(max_length=100, blank=False)
email = models.CharField(max_length=100, blank=True, default='')
password = models.CharField(max_length=100, blank=True, default='')
timestamp = models.DateTimeField(default= timezone.now)
class Meta:
ordering = ['timestamp']
class Profile(models.Model):
# choices [...]
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
profile_type = models.CharField(max_length=2,choices=PROFILE_CHOICES,default=TEAMMEMBER)
authentication_token = models.CharField(max_length=100, null=True)
avatar_url = models.CharField(max_length=100, default='')
permissions = models.CharField(max_length=100, null=True)
timestamp = models.DateTimeField(default= timezone.now)
class Meta:
ordering = ['timestamp']
serializer.py
class UserSerlializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['name', 'email', 'password']
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerlializer()
class Meta:
model = Profile
fields = ['user', 'profile_type']
def create(self, validated_data):
user_data = validated_data.pop('user')
profile = Profile.objects.create(**validated_data)
Profile.objects.create(user=profile, **user_data)
return Profile
POST
{
"profile_type" : "ST",
"user": {
"name" : "test",
"email" : "test#test.com",
"password" : "123456"
}
}
You are creating instances in wrong way. Change your create(...) method as,
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerlializer()
class Meta:
model = Profile
fields = ['user', 'profile_type']
def create(self, validated_data):
user_data = validated_data.pop('user')
user_instance = User.objects.create(**user_data)
profile_instance = Profile.objects.create(user=user_instance, **validated_data)
return profile_instance
Profile.user should beUser instance, but you are assigning Profile instance.
Change your create method to this:
class ProfileSerializer(serializers.ModelSerializer):
user = UserSerlializer()
class Meta:
model = Profile
fields = ['user', 'profile_type']
def create(self, validated_data):
user_data = validated_data.pop('user')
profile = Profile.objects.create(**validated_data)
user = User.objects.create(**user_data) # 1. creating user
profile.user = user # 2. assigning user
profile.save() # 3. saving profile after adding user
return profile # returning Profile instance.
inherit your user model from django contrib auth module also, and make a one to one relation with profile
from django.contrib.auth.models import User

Add Extra fields in serializer from one to one relation models

In this project, I have two models Student and Parent related to each other through one to one field.
In Parent serializer, I want to add Students attributes like age. I am thinking of using SerializerMethodField for both cases is their any better way to do it?
I am not getting the queries on how to get the object attributes and little explanation would be great.
Here what I have done till now.
Models.py
class Student(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, default=None)
batch = models.ForeignKey(Batch, on_delete=models.CASCADE, null=True, related_name='students')
email = models.EmailField(null=True)
phone_number = models.CharField(max_length=10, null=True)
dob = models.DateField(blank=True, null=True, help_text="Enter in the following format : YYYY-MM-DD")
address = models.TextField(max_length=150, null=True)
age = models.IntegerField(blank=True)
image = models.ImageField(upload_to='profile_pictures', default='student_image.png', blank=True)
#property
def remarks(self):
return self.remark_set.all()
#property
def marks(self):
return self.marks_set.all()
def __str__(self):
return self.user.firstName + ' ' + self.user.lastName
class Parent(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, default=None)
child = models.ForeignKey(Student, on_delete=models.CASCADE)
email = models.EmailField(null=True)
phone_number = models.CharField(max_length=10, null=True)
address = models.TextField(max_length=150, null=True)
image = models.ImageField(upload_to='profile_pictures', default='student_image.png', blank=True)
def __str__(self):
return self.user.firstName + ' ' + self.user.lastName
Serilaizer.py
class ParentSerializer(serializers.HyperlinkedModelSerializer):
student_age = serializers.SerializerMethodField()
student_batch = serializers.SerializerMethodField()
parent_name = serializers.SerializerMethodField()
class Meta:
model = Parent
fields = "__all__"
def get_student_age(self, obj):
return Parent.objects.get(child__age = self.obj.user.child)
def get_student_batch(self, obj):
return Parent.objects.get(child__bacth = self.obj.user.child)
def get_parent_name(self, user):
return Parent.objects.get(user=self.request.user)
Views.py
class ParentView( mixins.ListModelMixin, mixins.RetrieveModelMixin,viewsets.GenericViewSet):
queryset = Parent.objects.all()
serializer_class = serializers.ParentSerializer
first way:
from apps.models import Student, parent
class BasicUserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
class BasicStudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = "__all__"
class ParentSerializer(serializers.ModelSerializer):
user = BasicUserSerializer(read_only=True,many=False)
child = BasicStudentSerializer(read_only=True, many=True)
class Meta:
model = Parent
fields = '__all__'
you can do this way . its replace a serializer field that you want and if parent have several child then in child's field you have new all child's information as dictionary.
================================================================
second way is use HyperLinkModel .
class ParentSerializer(serializers.ModelSerializer):
user = serializers.HyperlinkedRelatedField(read_only=True,many=False)
child = serializers.HyperlinkedRelatedField(read_only=True, many=True)
class Meta:
model = Parent
fields = '__all__'
but notice that in first way you will have a independent serializer class that every time you need to serialize model class that related to User or Child you can use them simply.

add new record to many to many relation in django rest framework

I was trying to add new relation to many to many records,
for example i have these models:
models.py
class Team(models.Model):
name = models.CharField(blank=True, unique=True, max_length=100)
players = models.ManyToManyField(User, blank=True, related_name='players')
class TeamInvite(models.Model):
from_user = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name='invite_by', blank=True, null=True)
to_user = models.ForeignKey(User, on_delete=models.DO_NOTHING, related_name='invite_to', blank=True, null=True)
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name='invite_to_team', blank=True, null=True)
status = models.NullBooleanField(blank=True, null=True, default=None,)
and my serializer:
serializers.py
class TeamInviteCreateSerializer(serializers.ModelSerializer):
team = serializers.PrimaryKeyRelatedField(queryset=Team.objects.all())
from_user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta:
model = TeamInvite
fields = ('id', 'from_user', 'to_user', 'team', 'status')
after that the user which in to_user will take an action to TeamInvite like accept or decline.
I need the serializer which will take the new user and add him to the existing team like the following serializer:
class TeamInviteAcceptDeclineSerializer(serializers.ModelSerializer):
method_name = serializers.SerializerMethodField()
class Meta:
model = TeamInvite
fields = ('id', 'from_user', 'date_time', 'team', 'method_name', 'status')
def get_method_name(self, *args, **kwargs):
method_name = None # kwargs['context']['request'].method_name
return method_name
def update(self, instance, validated_data):
instance.team = validated_data.get('team', instance.team)
method_name = validated_data.get('method_name')
instance.status = validated_data.get('status', instance.status)
instance.to_user = validated_data.get('to_user', instance.to_user)
if method_name == 'decline':
instance.status = False
else:
instance.status = True
team = Team.objects.get(pk=instance.team.pk)
team.players.add(instance.to_user)
# team.players.create(team_id=team, user_id=instance.to_user)
team.save()
instance.save()
return instance
update function does not add the user to existing team and doesn't raise any error either. What am i missing here?
my request was:
{
"from_user": 1,
"to_user": 23
"team": 64,
"method_name": "accept",
"status": null
}
thank you
I got the missing point in my code..
it was in:
class TeamInviteAcceptDeclineSerializer(serializers.ModelSerializer):
method_name = serializers.SerializerMethodField()
class Meta:
model = TeamInvite
fields = ('id', 'from_user', 'date_time', 'team', 'method_name', 'status')
fields = missed 'to_user' pram

an api for adding label to device

I am trying to add gmail feature of moving the mail to custom created label or already made label. It is a move to label feature. When you select the mail and then click on move to label actions, you will see lots of pre-defined label name such as important, sms, personal, work etc and also a create a new label in the dropdown. I want to have get, post and delete. Am i on the right track?
Here is my model
class Device(BaseDevice):
"""
This stores Device
"""
# Description is optional (can be blank, can be null)
description = models.TextField(blank=True, null=True)
created_on = models.DateTimeField(auto_now_add=True)
updated_on= models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
class Label(models.Model):
"""
This creates a label
"""
# Name is required
name = models.CharField(max_length=250, blank=False, null=False)
device = models.ForeignKey(Device, related_name='label')
Serializers
class IOSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex')
class Meta:
model = IO
fields = ('id', 'name', 'description', 'type')
class LabelSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex')
class Meta:
model = Label
fields = ('id', 'name')
class DeviceSerializer(serializers.ModelSerializer):
id = serializers.UUIDField(source='token', format='hex', read_only=True)
label = LabelSerializer(read_only=False, many=True, required=False)
io = IOSerializer(read_only=False, many=True, required=False)
class Meta:
model = Device
fields = ('id', 'name', 'description', 'io', 'label')
def save_io(self, device, io):
for io in io:
token = io.pop('token', None)
if token is None:
IO.objects.create(device=device, **io)
else:
IO.objects.filter(token=token, device=device).update(**io)
def update(self, instance, validated_data):
io = validated_data.pop('io',[])
instance = super(DeviceSerializer, self).update(instance, validated_data)
if len(io):
self.save_io(instance, io)
return instance
def create(self, validated_data):
io = validated_data.pop('io',[])
instance = super(DeviceSerializer, self).create(validated_data)
if len(io):
self.save_io(instance, io)
return instance

Categories

Resources