How to serialize a property from another model in django rest? - django-rest-framework

I have two models and two serializers. I'm trying to use a property from one this models.
I already tried using foo = serializers.ReadOnlyField() but doesn't work.
My example:
class ExampleOne(models.Model):
example_three = models.ForeignKey(
"foo.ExampleThree",
on_delete=models.CASCADE,
related_name="ones",
)
#property
def foo(self):
return bar
class ExampleTwo(models.Model):
example_four = models.ForeignKey(
"foo.ExampleFour",
on_delete=models.CASCADE,
related_name="twos",
)
class ExampleThree(models.Model):
example_four = models.ForeignKey(
"foo.ExampleFour",
on_delete=models.CASCADE,
related_name="threes",
)
class ExampleFour(models.Model):
...
fields=...
...
class ExampleOneSerializer(serializers.Serializer):
class Meta:
model = ExampleOne
fields = "__all__"
class ExampleTwoSerializer(serializers.Serializer):
foo = serializers.ReadOnlyField()
class Meta:
model = ExampleTwo
fields = "__all__"
Does anyone have any idea how I can get this porperty from another serializer?

Related

DRF Serializing inherited models

I have two models inheriting same model and i want to serialize them. I couldn't see a way to do it with modelserializer class.
class Answer(models.Model):
submission = models.ForeignKey( Submission, related_name='answers', on_delete=models.CASCADE)
question = models.ForeignKey( Question, related_name='answers', on_delete=models.CASCADE )
class TextAnswer(Answer):
answer = models.CharField(max_length=10000 , blank=True)
class IntegerAnswer(Answer):
answer = models.IntegerField(blank=True)
What i want to do is this so i can serialize all the related answers no matter what type they are.
class QuestionSerializer(serializers.ModelSerializer):
answers = AnswerSerializer(many=True)
class Meta:
model = Question
fields = '__all__'
One approach I can think of is to use SerializerMethodFields:
class QuestionSerializer(serializers.ModelSerializer):
answers = serializers.SerializerMethodField()
class Meta:
model = Question
fields = '__all__'
def get_answers(self, obj)
return obj.answers.all().values_list('answer', flat=True)

DRF - Add User to a model with ManyToManyField

I am trying to implement a feature to my backend and allow the owner of private "Group" to add other users by their usernames instead of ID's and allow them to add their images to FileField only once after they were added to the model. The code I have so far:
models.py
class Group(models.Model):
group_name = models.CharField(max_length=255)
group_text = models.TextField(max_length=360, blank=True)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL, related_name='owner_user', on_delete=models.SET(get_deleted_user), default=1)
created_on = models.DateTimeField(auto_now_add=True, null=True)
shared_to = models.ManyToManyField(UserProfile, blank=True, related_name='shared_to_user', null=True)
def __str__(self):
return self.group_name
def save(self, *args, **kwargs):
super(Group, self).save(*args, **kwargs)
class GroupImage(models.Model):
group_file = models.FileField(blank=True, null=True,
upload_to='media/covers/%Y/%m/%D/')
gallery_group = models.ForeignKey(Group, related_name='images', on_delete=models.CASCADE)
serializers.py
class GroupImageSerializer(serializers.ModelSerializer):
class Meta:
model = models.GroupImage
fields = ('group_file', )
class SharedToSerializer(serializers.ModelSerializer):
class Meta:
model = models.Group
fields = ('shared_to', )
class GroupSerializer(serializers.ModelSerializer):
images = GroupImageSerializer(many=True, read_only=True)
person = SharedToSerializer(many=True, read_only=True)
class Meta:
model = models.Group
fields = ('id', 'group_name', 'group_text', 'person', 'images')
def create(self, validated_data):
images_data = self.context.get('view').request.FILES
owner_id = self.context['request'].user.id
gallery_group = models.Group.objects.create(group_name=validated_data.get('group_name', 'no-
group_name'), group_text=validated_data.get('group_text'), owner_id=1)
for image_data in images_data.values():
models.GroupImage.objects.create(gallery_group=gallery_group,
group_file=image_data)
return gallery_group
views.py
class GroupCreateAPIView(generics.CreateAPIView):
queryset = models.Group.objects.all()
serializer_class = serializers.GroupSerializer
permission_classes = [AllowAny]
So if your only requirement is how to add users by their username and not their id. You should use SlugRelatedField. I also feel your serializer naming convention is quite confusing. Below is the serializer for Group model that can add users to a group.
class GroupSerializer(Serializer):
... other fields here
shared_to = models.SlugRelatedField(queryset = UserProfile.objects.all(), many=True, slug_field="username", allow_empty=True)
So first checkout SlugRelatedField. This basically is used to map to objects using a specific field of that object(username in this case). You will then get all the UserProfile instances in the shared_to field of the validated_data
property of the serializer which you can fetch in create method and add to you group. And then in the file upload api for your group you can check whether this user belongs to the group or not for permission checking.

How represent a many to many relationship with django rest framework using Hyperlinking

I have two models in a 'many to many' relationship Video and Genre. I want to represent them in my API using the Django Rest Framework and with Hyperlinking. I have tried using the the nested relationships solution that I have found over the internet but it brings me an error for the list viewss eg genres/ and videos/ as described below.
models.py
class VideoGenre(models.Model):
video = models.ForeignKey(Video, on_delete=models.CASCADE, related_name='genres')
genres = models.ForeignKey(Genre, on_delete=models.CASCADE, related_name='videos')
class Meta:
unique_together = ['video', 'genre']
class Video(models.Model):
genres = models.ManyToManyField(Genre, related_name="videos", blank=True, through='VideoGenre')
title = serializers.CharField(default=serializers.CurrentUserDefault())
class Genre(models.Model):
name = models.CharField(max_length=20)
descr = models.CharField(max_length=255)
serializers.py
class GenreSerializer(serializers.HyperlinkedModelSerializer):
videos = serializers.HyperlinkedIdentityField(many=True, view_name="video-detail")
class Meta:
model = ku_api_models.Genre
fields = "__all__"
class VideoSerializer(serializers.HyperlinkedModelSerializer):
genres = GenreSerializer(source='genre_set', many=True, read_only=True)
class Meta:
model = ku_api_models.Video
fields = "__all__"
class VideoGenreSerializer(serializers.HyperlinkedModelSerializer):
video = serializers.ReadOnlyField(source='video.id')
genre = serializers.ReadOnlyField(source='genre.id')
class Meta:
model = ku_api_models.VideoGenre
fields = "__all__"
unique_together = ['video', 'genre']
views.py
class VideoDetailView(RetrieveUpdateDestroyAPIView):
parser_classes = (MultiPartParser,)
serializer_class = VideoSerializer
queryset = Video.objects.all()
def perform_update(self, serializer):
serializer.save(owner=self.request.user)
class VideoListView(ListCreateAPIView):
parser_classes = (MultiPartParser,)
serializer_class = VideoSerializer
queryset = Video.objects.all()
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class GenreListView(ListCreateAPIView):
serializer_class = GenreSerializer
queryset = Genre.objects.all()
class GenreDetailtView(RetrieveUpdateDestroyAPIView):
serializer_class = GenreSerializer
queryset = Genre.objects.all()
urls.py
path('videos/', views.VideoListView.as_view(), name='video-list'),
path('videos/<int:pk>', views.VideoDetailView.as_view(), name='video-detail'),
path('genres/', views.GenreListView.as_view(), name="genre-list"),
path('genres/<int:pk>', views.GenreDetailtView.as_view(), name="genre-detail"),
I am getting below exception on the browsable API renderer
Exception Type: ProgrammingError at /api/genres/
Exception Value: relation "ku_api_videogenre" does not exist
LINE 1: ..._video"."owner_id" FROM "ku_api_video" INNER JOIN "ku_api_vi...
I have looked into these below links as referred above
django rest framework serializing many to many field
many to many relationship through an intermidiate model
serializing many to many field
... and tried them but getting the same/almost the same, only different is the relation name that "does not exist"
What am I missing? How to make it right? Thanks
I have found a way to this:
Changes will be in the serializers.py
serializers.py
class VideoSerializer(serializers.HyperlinkedModelSerializer):
genres = serializers.HyperlinkedRelatedField(many=True, read_only=False, view_name='genre-detail', queryset=ku_api_models.Genre.objects.all())
class Meta:
model = ku_api_models.Video
#fields = ('url', 'id', 'datetime_added', 'title', 'video_file', 'owner')
fields = "__all__"
def create(self, validated_data):
genres = validated_data.pop('genres', [])
video_instance = ku_api_models.Video.objects.create(**validated_data)
video_instance.genres.set(genres)
return video_instance
def update(self, instance, validated_data):
genres = validated_data.pop('genres', [])
instance.genres.set(genres)
instance.save()
return instance
class VideoGenreSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ku_api_models.VideoGenre
fields = "__all__"
class GenreSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ku_api_models.Genre
fields = "__all__"
Source: Docs: intermediary-manytomany

Is there more better cancel nested ManyToMany field unique check

# models.py
class Student(models.Model):
name = models.CharField( max_length=256)
student_numer = models.CharField(max_length=256)
teachers = models.ManyToManyField(Teacher)
class Teacher(models.Model):
name = models.CharField(max_length=256)
# serializers.py
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ('name', 'student_number', )
class TeacherSerializer(serializers.ModelSerializer):
students = StudentSerializer(many=True)
class Meta:
model = Teacher
fields = '__all__'
def update(self, instance, validated_data):
students = validated_data.pop('students')
# here I want delete all old students, and add all new students
# of course, I have more better way to update it, but here just for simple.
But in fact, if I update teacher instance, and if the students not change, it will
raise: student with this student_numer already exist.
I know why, because StudentSerializer's student_numer field has the validator check unique.
And I can add some code like this to fixed this problem:
class StudentSerializer(serializers.ModelSerializer):
class Meta:
model = Student
fields = ('name', 'student_number', )
extra_kwargs = {
'student_numer': {
'validators':[]
}
}
Now I want to know is there any more better way ??

ModelSerializer using model property

I'm trying to serialize a model containing a property field that I also want to serialize.
models.py:
class MyModel(models.Model):
name = models.CharField(max_length=100)
slug = models.AutoSlugField(populate_from='name')
#property
def ext_link(self):
return "/".join([settings.EXT_BASE_URL, self.slug])
serializers.py:
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ('name', 'ext_link')
When trying to get to the related URL, I'm getting a serializer exception (KeyError) on the ext_link property.
How can I serialize the ext_link property?
Because it's not a model field, it needs to be added explicitly to the serializer class
class MyModelSerializer(serializers.ModelSerializer):
ext_link = serializers.Field()
class Meta:
model = MyModel
fields = ('name', 'ext_link')
as #Robert Townley's comment, this work with version 3.8.2:
class MyModelSerializer(serializers.ModelSerializer):
ext_link = serializers.ReadOnlyField()
class Meta:
model = MyModel
fields = "__all__"
The accepted answer doesn't seem to work for me, nor does the ReadOnlyField.
However, I have had success when I use a field that corresponds to the return type of my property function.
So for the example, I would do this:
class MyModelSerializer(serializers.ModelSerializer):
ext_link = serializers.CharField()
class Meta:
model = MyModel
fields = ('name', 'ext_link')
I've been able to do this with ListField, DictField, and IntegerField as well.
Another thing you might want to do is add a property that its contents are not a string. Let's say you have a model called Person and another one called Food that look like this (we assume that each food is the favorite of only one person, making it a OneToMany connection):
class Person(models.Model):
name = models.CharField(max_length=255)
#property
def favorite_foods(self):
return Food.objects.filter(person=self.pk)
class Food(models.Model):
name = models.CharField(max_length=255)
persons_favorite = models.ForeignKey(Person, on_delete=models.CASCADE)
If you want to add favorite_foods in Person's serializer all you have to do is:
class PersonSerializer(serializers.ModelSerializer):
favorite_foods = FoodSerializer(read_only=True, many=True)
class Meta:
model = Person
fields = ('name', 'favorite_foods')

Resources