Reputation: 6744
Is there a way to add custom data to the results data returned by a list API call to a view that uses Django REST Framework's ModelViewSet?
My ModelViewSet view is attached to an Items model.
In addition to the list of Items that is returned by the LIST API call I want to query a separate Seen model and return, with the Items, the number of times each Item has been seen.
I also want to add a 'Quote of the day' to the returned data.
I have searched the DRF documentation and haven't been able to find any mention of how to do this.
Here is my code. The Item serializer is:
class ItemSerializer(serializers.ModelSerializer):
username = serializers.SerializerMethodField()
def get_username(self, obj):
"""
Note that query params can be accessed here as follows:
request = self.context['request']
print request.query_params.get['fields']
"""
value = str(obj.owner)
return value
def get_keywords(self, obj):
value = str(obj.keywords)
return value
class Meta:
model = Item
fields = ('id', 'url', 'item_type', 'title', 'credits_applied', 'credits_left', 'credits_gifted', 'username', 'liked', 'disliked')
The View looks like this:
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all().order_by('-date_added')
serializer_class = ItemSerializer
# When POSTing (creating) a new item add appropriate User instance to the serializer
def perform_create(self, serializer):
creator = User.objects.get(pk=self.request.data['owner_id'])
serializer.save(owner=creator)
def get_queryset(self):
this_user = self.request.query_params.get('user', None)
restrict_to_items_from_user_id = self.request.query_params.get('from', None)
quantity = self.request.query_params.get('num', 20)
# Use query params to determine what to return
if restrict_to_items_from_user_id is not None:
# API: /api/items/from=<id>
# Return items owned by a specific user. Used by someone to get a list of the items they have added
queryset = Item.objects.filter(owner=restrict_to_items_from_user_id, active=True).order_by('-date_added')[0:int(quantity)]
elif this_user is not None:
# API: /api/items/user=<id>
# Return unseen items for the given user's browse feed
# queryset = Item.objects.all().order_by('-date_added')[0:int(quantity)]
queryset = Item.objects.filter(active=True, credits_left__gt=0).exclude(pk__in=Seen.objects.filter(user_id=this_user).values_list('item_id', flat=True))[0:int(quantity)]
# :TO DO: Add option to list the items a user has liked!
else:
# API: /api/items
# Return items not specific to a particular user (used for testing the app or when user wants to see stuff they have seen before)
queryset = Item.objects.filter(active=True, credits_left__gt=0)[0:int(quantity)]
return queryset
The Item and Seen models are:
class Item(models.Model):
ITEM_TYPES = (
('V', 'Vine'),
('Y', 'YouTube'),
('P', 'Photo'), # Photo is stored by us on a CDN somewhere
('F', 'Flickr'),
('I', 'Instagram'),
('D', 'DeviantArt'),
('5', '500px'),
)
owner = models.ForeignKey(User, on_delete=models.CASCADE) # Id of user who owns the item
title = models.CharField(max_length=60, default='') # URL of where item resides (e.g. Vine or YouTube url)
url = models.CharField(max_length=250, default='', unique=True)
# URL of where item resides (e.g. Vine or YouTube url)
item_type = models.CharField(max_length=1, choices=ITEM_TYPES) # Type of item (e.g. Vine|YoutTube|Instagram|etc.)
keywords = models.ManyToManyField(Keyword, related_name='keywords')
# E.g. Art, Travel, Food, etc.
credits_applied = models.IntegerField(default=10, help_text='Total number of credits applied to this item including any given by VeeU admin')
# Records the total number of credits applied to the Item
credits_left = models.IntegerField(default=10, help_text='The number of credits still remaining to show the item')
# Number of credits left (goes down each time item is viewed
credits_gifted = models.IntegerField(default=0, help_text='The number of credits this item has been gifted by other users')
# Number of credits users have gifted to this item
date_added = models.DateTimeField(auto_now_add=True) # When item was added
liked = models.IntegerField(default=0) # Number of times this item has been liked
disliked = models.IntegerField(default=0) # Number of times this item has been disliked
active = models.BooleanField(default=True, help_text='If you mark this item inactive please say why in the comment field. E.g. "Inapproriate content"')
# True if item is available for showing
comment = models.CharField(max_length=100, blank=True) # Comment to be applied if item is inactive to say why
# Add defs here for model related functions
# This to allow url to be a clickable link
def item_url(self):
return u'<a href="%s">%s</a>' % (self.url, self.url)
item_url.allow_tags = True
def __str__(self):
return '%s: Title: %s, URL: %s' % (self.owner, self.title, self.url)
# Record of which items have been viewed, when, and whether they were liked or not
class Seen(models.Model):
item_seen = models.ForeignKey(Item, on_delete=models.CASCADE) # id of the item that has been seen
who_saw = models.ForeignKey(User, on_delete=models.CASCADE) # id of user who viewed it
date_seen = models.DateTimeField(auto_now_add=True) # When item was viewed
liked = models.BooleanField(help_text='If the item was liked this is set to true')
class Meta:
unique_together = ('item_seen', 'who_saw',)
Upvotes: 0
Views: 11275
Reputation: 2106
If you use Paginators, you can use this:
from collections import OrderedDict
from rest_framework.pagination import LimitOffsetPagination
from rest_framework.response import Response
from rest_framework import viewsets
class CustomLimitOffsetPagination(LimitOffsetPagination):
def get_paginated_response(self, data):
return Response(
OrderedDict([
('count', self.count),
('next', self.get_next_link()),
('previous', self.get_previous_link()),
('results', data),
('extra_field', <your_value>),
]))
class SomeViewset(viewsets.ModelViewSet):
pagination_class = CustomLimitOffsetPagination
Upvotes: 2
Reputation: 6933
If you want to attach the new data to the regular response, you can do this:
class ItemViewSet(viewsets.ModelViewSet):
...
def list(self, request, *args, **kwargs):
custom_data = {
'list_of_items': ItemSerializer(self.get_queryset(),many=true).data # this is the default result you are getting today
'quote_of_the_day': <code to compute Quote of the day>
'number_of_times': <code to compute number of times>
}
return Response(custom_data)
...
although, and judging by your comment: "the number of times each Item has been seen." the number of time looks like it is a new property for each item, so you also can do:
class ItemViewSet(viewsets.ModelViewSet):
...
def list(self, request, *args, **kwargs):
custom_data = {
'list_of_items': ItemSerializer(self.get_queryset(), many=true).data # this is the default result you are getting today
'quote_of_the_day': <code to compute Quote of the day>
})
return Response(custom_data)
...
class ItemSerializer(serializers.ModelSerializer):
...
number_of_times = serializers.SerializerMethodField()
...
def get_number_of_times(self):
# compute and return the number of times the Item has been seen.
...
Upvotes: 6