Bill Noble
Bill Noble

Reputation: 6744

Add data to the list returned by Django REST Framework's ModelViewSet

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

Answers (2)

&#214;zer
&#214;zer

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

trinchet
trinchet

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

Related Questions