New to DRF (and REST in general).
Models:
class Event(models.Model):
name = models.CharField()
class Result(models.Model):
event = models.ForeignKey(Event, related_name='results')
person = models.CharField()
score = models.IntegerField()
Serializers:
class ResultSerializer(serializers.ModelSerializer):
class Meta:
model = Result
class EventSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Event
Viewsets:
class ResultViewSet(viewsets.ModelViewSet):
queryset = Result.objects.all()
serializer_class = ResultSerializer
class EventViewSet(viewsets.ModelViewSet):
queryset = Event.objects.all()
serializer_class = EventSerializer
Router:
router = routers.DefaultRouter()
router.register(r'events', EventViewSet)
URL:
urlpatterns = [
url(r'^api/', include(router.urls)),
]
This works fine, I can go to http://mysite/api and see "events" with a link to http://mysite/api/events/. From there each event has a link to http://mysite/api/events/id. So far so good.
If I change the event serializer to this, it will also include all the results (for that event) from Result:
class EventSerializer(serializers.HyperlinkedModelSerializer):
results = ResultSerializer(many=True, read_only=True)
class Meta:
model = Event
This also works fine. But I don't want the (often very long) results to be included for each event at http://mysite/api/events. There are way too many. I'd only like to see results included when I go to http://mysite/api/events/id.
Any tips on how I can get from where I am to where I want to be? It would be even better if each item on http://mysite/api/events included a count of the results, then http://mysite/api/events/id actually had the results.
Hope this made sense.
Thanks.
We can create 2 serializers EventSerializer and EventDetailSerializer which will return different serialized representations based on the type of request.
class EventSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Event
class EventDetailSerializer(serializers.HyperlinkedModelSerializer):
results = ResultSerializer(many=True, read_only=True) # contain the serialized results
class Meta:
model = Event
Then we will override the get_serializer_class() of EventViewSet which return EventDetailSerializer in case of retrieve requests and EventSerializer otherwise.
class EventViewSet(viewsets.ModelViewSet):
queryset = Event.objects.all()
def get_serializer_class(self):
if self.action == 'retrieve': # check if a 'retrieve' request
return EventDetailSerializer
return EventSerializer # otherwise return this serializer
Related
I need to create a DRF list view that shows each course along with a boolean field signifying whether the user requesting the view is subscribed to the course.
Course subscriptions are stored in the following model:
class Subscription(models.Model):
user = models.ForeignKey(
CustomUser, related_name='subscriptions', null=False,
on_delete=CASCADE)
course = models.ForeignKey(
Course, related_name='subscriptions', null=False,
on_delete=CASCADE)
class Meta:
ordering = ['course', 'user']
unique_together = [['user', 'course']]
This is the view I am writing:
class CourseListView(generics.ListAPIView):
permission_classes = [IsAuthenticated, ]
queryset = Course.objects.all()
serializer_class = CourseSerializer
def isSubscribed(self, request, course):
sub = Subscription.objects.filter(
user=request.user, course=course).first()
return True if sub else False
def list(self, request, format=None):
queryset = Course.objects.all()
serializer = CourseSerializer(queryset, many=True)
return Response(serializer.data)
I am looking for a way to modify the list method, so as to add to the response the information about whether request.user is subscribed to each of the courses.
The best solution I have for now is to construct serializer manually, which would look (at the level of pseudo-code) something like this:
serializer = []
for course in querySet:
course['sub'] = self.isSubscribed(request, course)
serializer.append(CourseSerializer(course))
I suspect there should be a better (standard, idiomatic, less convoluted) way for adding a custom field in a list view, but could not find it. In addition, I am wondering whether it is possible to avoid a database hit for every course.
You can do that easily with Exists:
just change your queryset in your view:
from django.db.models import Exists, OuterRef
class CourseListView(generics.ListAPIView):
permission_classes = [IsAuthenticated, ]
serializer_class = CourseSerializer
def get_queryset(self):
subquery = Subscription.objects.filter(user=request.user, course=OuterRef('id'))
return Course.objects.annotate(sub=Exists(subquery))
and add a field for it in your serializer:
class CourseSerializer(serializers.ModelSerializer):
sub = serializers.BooleanField(read_only=True)
class Meta:
model = Course
fields = '__all__'
I'm using Django 2.2 and Django REST Framework.
I have the following model structure
class MyModel(models.Model):
name = models.ChartField(max_length=200)
class Tag(models.Model):
name = models.ChartField(max_length=50, unique=True)
class MyModelRelation(models.Model):
obj = models.ForeignKey(MyModel, related_name='relation')
user = models.ForeignKey(User)
tags = models.ManyToManyField(Tag)
def tag_list(self):
return self.tags.all().values_list('name', flat=True).distinct()
I want to get the tags list with the MyModel instance and for that, the serializer is
class MyModelSerializer(serializers.ModelSerializer):
tags_list = serializers.SerializerMethodField(read_only=True)
def get_tags_list(self, obj):
return obj.relation.tag_list()
class Meta:
fields = [
'name',
'tags_list'
]
and the view is
class ObjListView(ListAPIView):
serializer_class = MyModelSerializer
def get_queryset(self):
return super().get_queryset().select_related('relation').prefetch_related('relation__tags')
But to get 58 records, it is running almost 109 queries.
The my_app_mymodel`, `my_app_mymodelrelation_tags is repeated multiple times
This is how I suggest you solve the problem. Instead of extracting the name in the DB level, you can do it in the serializer level. It will make things way easier and faster. First, remove the tag_list method from the model class. First add the annotation to your views:
from django.db.models import F
def get_queryset(self):
return super().get_queryset().annotate(tags_list=F('relation__tags')).select_related('relation')
Then in your serializers
class MyModelSerializer(serializers.ModelSerializer):
tags_list = serializers.SlugRelatedField(many=True, slug_field='name', read_only=True)
...
It is my first project using Django rest framework and i'm struggling to get this right. I know that in mainstream Django framework, if i need to add extra contexts to a class-based view, i will do something like this:
class PostDetail(DetailView):
model = Post
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add in a QuerySet of all the books by a certain author John
context['john_books'] = Book.objects.filter(author=john)
return context
then, i will be able to access the context 'john_books' in my template. Now i need to do same by passing extra contexts to my PostViewSets. On the detail view, i want to access the list of post authored by that post author in my api endpoint (something like 'Posts from the same author'). I have read about get_serializer_context but still can't figure out how to implement it. This is what i have so far:
class PostViewSets(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_serializer_context(self):
context = super(PostViewSets, self).get_serializer_context()
author = self.get_object.author
author_posts = self.get_queryset().filter(author=author)
context.update({'author_posts': author_posts})
return context
i get this error:
AttributeError at /posts/ 'function' object has no attribute 'author'
My Post Model:
class Post(models.Model):
title = models.CharField(max_length=100, unique=True)
body = models.TextField()
is_featured = models.BooleanField(default=True)
viewcount = models.IntegerField(default=0)
author = models.ForeignKey(User, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
and my PostSerializer class:
class PostSerializer(serializers.ModelSerializer):
author = UserSerializer()
class Meta:
model = Post
fields = ['id', 'title', 'body', 'author', 'viewcount', 'is_featured', 'created']
You have to use get_object as func, not as property:
class PostViewSets(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostSerializer
def get_serializer_context(self):
context = super(PostViewSets, self).get_serializer_context()
author = self.get_object().author
author_posts = self.get_queryset().filter(author=author)
context.update({'author_posts': author_posts})
return context
1. The Error Message
AttributeError at /posts/ 'function' object has no attribute 'author'
explicitly explains what and where is the problem:
author = self.get_object.author
I guess you tried to do something like this
author = self.get_object().author
2. A DRF ViewSet
responses with data serialized by corresponding Serializer. So you don't need to change the ViewSet, but update the Serializer with something like:
class PostListSerializer(serializers.ModelSerializer):
... some fields ...
class Meta:
model = Post
fields = [ ... some fields ... ]
class PostDetailsSerializer(serializers.ModelSerializer):
... some fields ...
author_posts = PostListSerializer(source="author.post_set", many=True, read_only=True)
class Meta:
model = Post
fields = [ ... some fields ... , 'author_posts']
or with SerializerMethodField
class PostDetailsSerializer(serializers.ModelSerializer):
... some fields ...
author_posts = serializers.SerializerMethodField()
def get_author_posts(self, obj):
return PostListSerializer(instance=obj.post_set.all(), many=True).data
class Meta:
model = Post
fields = [ ... some fields ... , 'author_posts']
I didn't try this exact code, but this is the main idea.
you can pass some context into serializers this way,
serializer = self.serializer_class(instance=self.get_object(),
context={'request': request}
)
I've been using select_related() to speed up a large DRF call with great success, but I've hit a wall.
My main serializer references two other serializers, and one of those references yet another serializer. I'm unsure as how to implement prefetching in the second level serializer.
serializer.py
class DocumentsThinSerializer(serializers.ModelSerializer):
class Meta:
model = Documents
fields = ('confirmed', )
class PersonThinSerializer(serializers.ModelSerializer):
documents = DocumentsThinSerializer()
class Meta:
model = Person
fields = ('name', 'age', 'gender')
class EventThinSerializer(serializers.ModelSerializer):
day = DayThinSerializer()
person = PersonThinSerializer()
#staticmethod
def setup_eager_loading(queryset):
return queryset.select_related('day', 'person')
class Meta:
model = Event
views.py
class EventList(generics.ListAPIView):
authentication_classes = (SessionAuthentication, BasicAuthentication)
permission_classes = (IsAuthenticated,)
queryset = Event.objects.all()
serializer_class = EventThinSerializer
def get_queryset(self):
return self.get_serializer_class().setup_eager_loading(queryset)
As you can see, I'm using the static method setup_eager_loading() to get things going, but I can't find a queryset hook for my PersonThinSerializer() to get the speedup when accessing the DocumentsThinSerializer() in the same way.
Assuming Documents has a foreign key to Person, you should be able to add "person__documents" to your queryset.select_related in EventThinSerializer.setup_eager_loading:
class EventThinSerializer(serializers.ModelSerializer):
day = DayThinSerializer()
person = PersonThinSerializer()
#staticmethod
def setup_eager_loading(queryset):
return queryset.select_related('day', 'person', 'person__documents')
I use below solution to check is the user viewed the post or not.
Best way to make "viewed" attribute for messages inside user group?
and in django-rest-framework, i create a ListApiView to get all posts:
class PostListView(ListAPIView):
serializer_class = PostSerializer
permission_classes = (IsAuthenticated, )
pagination_class = PostListPagination
def get_queryset(self):
return Post.objects.filter(state='published').order_by('-created')
and the serializers:
class PostSerializer(serializers.ModelSerializer):
class Meta:
model = Post
fields= '__all__'
now i want a boolean field named "viewed" for each post in PostListView to show that is the authenticated user viewed this post or not.
something like this:
class PostSerializer(serializers.ModelSerializer):
viewed = serializers.BooleanField(read_only=True)
class Meta:
model = Post
fields= '__all__'
def check_is_viewed(current_user, post_instance):
# if user viewed this post:
viewed.value = True
# else:
viewed.value = False
You could use MethodField.
class PostSerializer(serializers.ModelSerializer):
viewed = serializers.SerializerMethodField()
class Meta:
model = Post
fields= '__all__'
def get_viewed(self, obj):
return obj.viewers.exist()