Passing argument from view to Custom RelatedField serializer - django-rest-framework

How can I pass an argument to a serializers.RelatedField class from views.py. I need to pass language_id to query Language.objects model within that RelatedField.
I am not sure if I took a right approach to this issue. What I want to achieve is to present information about genres associated to a movie from database model about depending on the language. The MovieGenre model has genre ID field which I want to replace with actual Genre name.
My serialiser.py
class GenreField(serializers.RelatedField):
def to_representation(self, value, language_id=1):
genre_name = GenresVideo.objects.get(genre_id=value, language_id=language_id)
return genre_name.name
class MovieGenresSerializer(serializers.ModelSerializer):
genre_id = GenreField(read_only=True)
class Meta:
model = MoviesGenres
As you see, here I query Language.objects with default value but I would like to pass it from views (language_id).
My views.py:
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer
def list(self, request, language_pk):
queryset = MoviesGenres.objects.all()
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
def retrieve(self, request, movie, language_pk):
queryset = MoviesGenres.objects.filter(movie=movie)
serializer = MovieGenresSerializer(queryset, many=True)
return Response(serializer.data)
And my urls.py:
router.register(r'lang', LanguagesViewSet, base_name='lang')
mov_gen = routers.NestedSimpleRouter(router, r'lang', lookup='language')
mov_gen.register(r'mg', MovieGenresTestViewSet, base_name='mg')
url(r'^api/', include(genre_spec.urls))
My models.py
class Languages(models.Model):
name = models.CharField(unique=True, max_length=255)
short_name = models.CharField(unique=True, max_length=4, blank=True, null=True)
active = models.BooleanField(default="")
class Meta:
managed = False
db_table = 'languages'
ordering = ('id',)
class GenresVideo(models.Model):
genre_id = models.IntegerField()
language = models.ForeignKey('Languages')
name = models.CharField(max_length=255, blank=True, null=True)
class Meta:
managed = False
db_table = 'genres_video'
unique_together = (('genre_id', 'language'),)
ordering = ('genre_id',)
class MoviesGenres(models.Model):
movie = models.ForeignKey(Movies)
genre_id = models.IntegerField()
class Meta:
managed = False
db_table = 'movies_genres'
unique_together = (('movie', 'genre_id'),)
Through the urls routes, I can get a correct response from API including the language_id. I just need to pass it to the view somehow.
Thanks a lot for help!

I'll try to answer to your first question, with the easiest implementation possible: SerializerMethodField. Because we will get the language id via the context passed to the serializer, we should either generate the context for the serializer, or let the framework do that for us.
Now to the problem at hand: you aren't filtering the queryset (MoviesGenres) by language per se. Thus, we can avoid overwriting the list and retrieve methods. Nevertheless, the router mechanism will inject in kwargs for the view method the language_pk parameter - that's the parameter that we will retrieve from within the serializer context:
class MovieGenresSerializer(serializers.ModelSerializer):
genre = searializers.SerializerMethodField()
class Meta:
model = MoviesGenres
def get_genre(self, instance):
# get the language id from the view kwargs
language_id = self.context['view'].kwargs['language_pk']
# get the genre
try:
genre_name = GenresVideo.objects.get(genre_id=instance.genre_id, language_id=language_id).name
except GenresVideo.DoesNotExist:
genre_name = None
# return the formatted output
return genre_name
class MovieGenresTestViewSet(viewsets.ModelViewSet):
lookup_field = 'movie'
queryset = MoviesGenres.objects.all()
serializer_class = MovieGenresSerializer

Related

TypeError: Field 'id' expected a number but got <class 'rest_framework.fields.CurrentUserDefault'>

Seems I need to apply a dot notation to CurrentUserDefault() object, tried .id but failed
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self,obj):
queryset=TagPrivate.objects.filter(user=serializers.CurrentUserDefault) # <--TypeError
return TagPrivateSerializer(queryset).data
models.py
class DotPrivate(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE
)
name = models.CharField(max_length=255)
description = models.TextField(max_length=350, blank=True)
lon = models.CharField(max_length=20)
lat = models.CharField(max_length=20)
rating = models.FloatField(validators=[MinValueValidator(0.0), MaxValueValidator(5.0)])
tag = models.ForeignKey('TagPrivate', on_delete=models.PROTECT)
in the following link in the first answer I found some solution but I do not completly understand it:
'CurrentUserDefault' object has no attribute 'user'
class TagPrivateSerializer(serializers.ModelSerializer):
class Meta:
model = TagPrivate
fields = ('id', 'name')
read_only_fields = ('id',)
You can not use CurrentUserDefault, this is just a value that the Django serializer will use for a default=… parameter, and then later swap for the request user.
You can fetch this from the request in the context, so:
class DotPrivateSerializer(serializers.ModelSerializer):
tag = serializers.SerializerMethodField()
def get_tag(self, obj):
queryset = TagPrivate.objects.filter(user=self.context['request'].user)
return TagPrivateSerializer(queryset).data
In the ModelViewSet, you will need to pass the user, so:
class DotPrivateViewSet(ModelViewSet):
queryset = # …
permission_classes = # …
serializer_class = DotPrivateSerializer
def get_serializer_context(self):
context = super().get_serializer_context()
context.update(request=self.request)
return context

Add annotated value in json response (django rest)?

I got a model that looks like this:
class Book(models.Model):
author = models.CharField(max_length=100)
chapters = models.IntegerField()
name = models.CharField(max_length=100)
and a "grand child" linked together through a Chapter model:
class Verse(models.Model):
chapter = models.ForeignKey(Chapter, on_delete=CASCADE)
verse = models.TextField()
verse_number = models.IntegerField()
I wish to get the count of all verses that belongs to a book, and I'm fetching them like this:
Book.objects.annotate(Count('chapters', distinct=True), total_num_verses=Count('chapter__verse', distinct=True))
However, I'm not sure how to get this into my serializer. I was thinking of using a SerializerMethodField The goal is to get the total_num_verses as a key/value pair in my json response
class BookSerializer(serializers.ModelSerializer):
total_verse_count = serializers.SerializerMethodField()
class Meta:
model = Book
fields = "__all__"
# this doesn't work..
def get_total_verse_count(self, obj):
print(obj.get_verses)
return self.annotate(Count('chapters', distinct=True), total_num_verses=Count('chapter__verse', distinct=True))
I just get 'BookSerializer' object has no attribute 'annotate'
Should I make a #property method in the Book model class itself?
views.py is just a plain APIView
class BookAPIView(APIView):
"""
List all books
"""
def get(self, request, format=None):
books = Book.objects.all()
serializer = BookSerializer(books, many=True)
return Response(serializer.data)
An effective way to do that is adding a readonly=True field in the serializer:
class BookSerializer(serializers.ModelSerializer):
total_verse_count = serializers.IntegerField(read_only=True)
class Meta:
model = Book
fields = ['author', 'chapters', 'name', 'total_verse_count']
In the view, you then pass the annotated queryset to the serializer:
class BookAPIView(APIView):
def get(self, request, format=None):
books = Book.objects.annotate(
total_verse_count=Count('chapter__verse')
)
serializer = BookSerializer(books, many=True)
return Response(serializer.data)

Update method of serializer only creates and doesn't update (Django rest framework)

I'm having a hard time with nested serializers, especially updating them. So far I can update the lesson description and all the main fields before the nested serializer (like course description, title etc). Ideally I'd like to search using lesson_id, and not lesson.title like it is now, and update description and title at the same time. Is there a workaround for that?
My models.py
class Course (models.Model):
title = models.CharField (max_length=150)
description = models.CharField(max_length=250, default="No Description")
student = models.ManyToManyField(Student, related_name='courses', blank=True)
teacher = models.ManyToManyField(Teacher, related_name='teacher', blank=True)
def __str__(self):
return self.title
class Lesson (models.Model):
title = models.CharField(max_length=100)
description = models.TextField(default="No Description")
course = models.ForeignKey(Course, on_delete=models.CASCADE, related_name='lessons')
def __str__(self):
return self.title
Serializers.py where the problem is
class CourseSerializer (serializers.ModelSerializer):
lessons = LessonSerializer(many=True, required=False)
#teacher = TeacherSerializer(many=True)
class Meta:
model = Course
fields = ('id', 'title', 'description', 'lessons') #to separate serializer with students for teachers later
def update(self, instance, validated_data):
lessons = validated_data.pop('lessons', [])
instance = super().update(instance, validated_data)
for lesson in lessons:
lesson, updated = Lesson.objects.update_or_create( defaults={'description': lesson["description"]}, title= lesson["title"])
#pk = instance.lessons_id doesn't work, I cannot get this id
instance.save()
return instance
Views
class CourseDetailDeleteView (generics.RetrieveUpdateDestroyAPIView):
permission_classes = (IsAdminUserOrAuthenticatedOrReadOnly,)
queryset = Course.objects.all()
serializer_class = CourseSerializer
def update(self, request, *args, **kwargs):
serializer = CourseSerializer(instance=self.get_object(), data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
If your problem is that validated_data["lessons"][0]["id"] doesn't exists:
By default the id field of a ModelSerializer is read_only, so id is not included in validated_data in .create() or .update(). Thus you'll have to override that:
class LessonSerializer(serializers.ModelSerializer):
id = serializers.IntegerField()
....
## Optional: in case you don't want `id` getting explicitly set
def create(self, validated_data):
validated_data.pop("id", None)
return super().create(self, validated_data)
## Optional: in case you don't want `id` of the instance getting updated
def update(self, instance, validated_data):
validated_data.pop("id", None)
return super().update(self, instance, validated_data)
Another note:
There's no way to know if serializer.save() is even called in CourseDetailDeleteView.update. I would suggest to set serializer.is_valid(raise_exception=True) instead so it can return error messages and show you why it didn't save.

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

How to restrict fields when creating post request in DRF?

I am making a POST api using DRF. In that api, I need only few fields(name, size, customer_name, customer_address), but don't require this fields(status, ordered_time) because these fields I want to save these fields in run time as status='open' and ordered_time=DateTimeField.now()
views.py
class PizzaOrderCustomerView(APIView):
def post(self, request):
orders = request.data.get('orders')
# Create an article from the above data
serializer = ArticleSerializer(data=orders)
if serializer.is_valid(raise_exception=True):
article_saved = serializer.save()
return Response({"success": "Article '{}' created successfully".format(article_saved.name)})
models.py
class PizzaOrder(models.Model):
name = models.CharField(max_length=120)
size = models.CharField(max_length=10, choices=SIZE_CHOICE, default='MEDIUM')
customer_name = models.CharField(max_length=120)
customer_address = models.TextField()
ordered_time = models.DateTimeField(default=timezone.now, editable=False)
status = models.CharField(max_length=20, default='open', editable=False)
serializers.py
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
# fields = '__all__'
read_only_fields = ('status',)
But when I try to create an order, it needed status and ordered_time also. But it should save at the time of creating order automatically.
Suggest a good way to do it.
from rest_framework import viewsets, mixins
class PizzaViewsets(viewsets.ViewSet, mixins.CreateModelMixin):
model = PizzaOrder
serializer_class = OrderSerializer
queryset = model.objects.all(
serializer, it is always good practise to mention all fields instead of
all
class OrderSerializer(serializers.ModelSerializer):
class Meta:
model = PizzaOrder
fields = ('status','ordered_time','name', 'size', 'customer_name', 'customer_address',)
read_only_fields = ('status','ordered_time',)

Resources