Django REST Framework - Combining and paginating multiple models - django

I am working with Django REST Framework and trying to combine two models, Tweet and Article into a single list - Feed. I want Feed to be a list API view that lists Tweet and Article object chronologically, which I've done so far. This is the code that I have...
class FeedViewSet(viewsets.ModelViewSet):
permission_classes = (AllowAny,)
serializer_class = FeedSerializer
paginate_by = 10
def list(self, request, *args, **kwargs):
results_list = list(chain(NewsArticle.objects.all(),
Tweet.objects.all()))
sorted_list = sorted(results_list, key=lambda instance: instance.date_added)
results = list()
for entry in sorted_list:
item_type = entry.__class__.__name__.lower()
if isinstance(entry, Tweet):
serializer = TweetSerializer(entry)
if isinstance(entry, Article):
serializer = ArticleSerializer(entry)
data_dict = {'item': item_type, 'data': serializer.data}
results.append(data_dict)
feed_ser = FeedSerializer(results, many=True)
return Response(feed_ser.data)
This works to the point I can access the data at the API endpoint I set up, however the pagination isn't working with this setup - all items are returned with no options for pagination.
I have followed this solution but I've tried both methods and I have no luck with pagination on either.
EDIT: I know an obvious solution would be to build a base class that they inherit from, and query that table/object, but unfortunately this isn't an option.
Thanks for the help!

Your code doesn't work for pagination because you are not calling the method list from the parent class. That is where the pagination is made, based on your queryset ( look here
at list method in ListModelMixin)
If you want pagination, I see two options:
-- either you implement it yourself, based on the way it is implemented in DRF (link above)
-- or you move all the code that builds your queryset (results) in the get_queryset method and let the framework do the pagination.
Good luck.

Related

Getting fields from extra manager methods using django-rest-framework

I have the following custom model manager in Django that is meant to count the number of related comments and add them to the objects query set:
class PublicationManager(models.Manager):
def with_counts(self):
return self.annotate(
count_comments=Coalesce(models.Count('comment'), 0)
)
Adding this manager to the model does not automatically add the extra field in DRF. In my API view, I found a way to retrieve the count_comments field by overriding the get function such as:
class PublicationDetails(generics.RetrieveUpdateAPIView):
queryset = Publication.objects.with_counts()
...
def get(self, request, pk):
queryset = self.get_queryset()
serializer = self.serializer_class(queryset.get(id=pk))
data = {**serializer.data}
data['count_comments'] = queryset.get(id=pk).count_comments
return Response(data)
This works for a single instance, but when I try to apply this to a paginated list view using pagination_class, overriding the get method seems to remove pagination functionality (i.e. I get a list of results instead of the usual page object with previous, next, etc.). This leads me to believe I'm doing something wrong: should I be adding the custom manager's extra field to the serializer instead? I'm not sure how to proceed given that I'm using a model serializer. Should I be using a basic serializer?
Update
As it turns out, I was using the model manager all wrong. I didn't understand the idea of table-level functionality when what I really wanted was row-level functionality to count the number of comments related to a single instance. I am now using a custom get_paginated_response method with Comment.objects.filter(publication=publication).count().
Original answer
I ended up solving this problem by creating a custom pagination class and overriding the get_paginated_response method.
class PaginationPublication(pagination.PageNumberPagination):
def get_paginated_response(self, data):
for item in data:
publication = Publication.objects.with_counts().get(id=item['id'])
item['count_comments'] = publication.count_comments
return super().get_paginated_response(data)
Not sure it's the most efficient solution, but it works!

Django REST Framework, display specific foreign key data objects

I'll get straight to my point
Here is my API output:
In pictures, I need to display objects that have primary_placeholder set to True.
I can't find answer on how to do it, I've tried searching in REST Api documentation but nothing seems to work, it always displays every single picture
serializers.py:
views.py
Models.py (Theese two that I'm currently working with)
Does anyone know what is the easiest and fastest way to do it? I have absolutely no idea and I've been trying figure it out for about 2 days now.
Database currently has no pictures with primary_placeholder set to True if anyone is confused
If want to show product pictures with primary_placeholder=True, we can use prefetch_related with Prefetch, please read this.
In your case above, i suggest:
views.py
from django.db import models
from your_app.models import Product, ProductPictures
class ProductList(APIView):
def get(self, request, format=None):
products = Product.objects.all().prefetch_related(
models.Prefetch(
"pictures",
queryset=ProductPictures.objects.filter(primary_placeholder=True)
)
)
serializer = ProductSerializer(products, many=True)
return Response(serializer.data)
Another suggest: In model ProductPictures field model, better if change the field name to product.
Good luck.
write a serializer method field and filter products in it.
class ProductSerializer(serializers.ModelSerializer):
filtered_pictures = serializers.MethodField()
class Meta:
model = Product
fields = ['brand','model','prize','filtered_pictures']
def get_filtered_pictures(self,obj):
if obj.pictures.exists():
queryset = obj.pictures.filter(primary_placeholder=True)
return ProductPictureSerializer(queryset, many=True).data
else:
return None

limit choices in drop downs in django rest browsable api

Is there a way to limit what fields are populated (such as in dropdown selectors or list selectors) in the DRF browsable API?
Below is an image example of how DRF is suggesting choices of "projects" to the user that he should select. However, the logged in user may or may not have access to these projects, so I'd like to get control over what shows up here! It seems that the default behavior is to show all related objects.
It would be really useful if there was a way to link the objects populated in these fields to be set according to a get_queryset() function.
This page seems to hint that it might be possible, I just can't find an example of how to do it: http://www.django-rest-framework.org/api-guide/filtering/
You can define a new field based on some of the serializers.RelatedField-ish classes, and use the request in the context to redefine what the method get_queryset returns.
An example that might work for you:
class AuthorRelatedField(serializers.HyperlinkedRelatedField):
def get_queryset(self):
if 'request' not in self.context:
return Author.objects.none()
request = self.context['request']
return Author.objects.filter(user__pk=request.user.pk)
class BookSerializer(serializers.HyperlinkedModelSerializer):
author = AuthorRelatedField(view_name='author-detail')
Check the templates in DRF/temapltes/rest_framework/inline/select.html. The select object in DRF uses the iter_options method of the field to limit the options in the select tag, which in turn uses the queryset from get_queryset.
This would efectively limit the options of the select tag used in the browsable API. You could filter as well in the get_choices method if you want to preserve the queryset (check the code in DRF/relations.py). I am unsure if this impacts the Admin.
I don't fully understand what do you want but try queryset filter's __in function if you need to show only exact values:
class PurchaseList(generics.ListAPIView):
serializer_class = PurchaseSerializer
def get_queryset(self):
"""
Optionally restricts the returned purchases to a given user,
by filtering against a `username` query parameter in the URL.
"""
to_show = [ "user1", "user2", "user3"]
queryset = Purchase.objects.all()
username = self.request.query_params.get('username', None)
if username is not None:
queryset = queryset.filter(purchaser__username__in=username)
return queryset
you can add your values to to_show list and if queryset element equals one of them then it will be shown.
Also if you want to show only some fields of model you need to edit your Serializer's fields parameter:
class PurchaseList(serializers.ModelSerializer):
class Meta:
model = Purchase
fields = ('id', 'field1', 'field2', ...)

django - How can I filter in serializer

class User(generics.RetrieveAPIView):
serializer_class = RetrieveLocalSerializer
queryset = User.objects.filter(
fields_1=True,
fields_2=False
)
class LocalSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('field_1', 'field_2', 'field_3',)
The API did not work as it I wish. When I tried get user that does not have the property i want, it still returned the result.
I even tried override that function but it did not work too.
def get_queryset(self):
return User.objects.filter(
is_localguide=True,
state=PROFILE_STATE.PUBLISHED
)
Any help is appreciated.
If I understood your question correctly you wish to get list of instances in your view (using Django Rest Framework). The problem is that your view is inheriting from a generics.RetrieveAPIView. This view class calls self.retrieve(request, *args, **kwargs) method which returns you an object, not queryset. I think that you should inherit your view from a ListAPIView class. This class inherits ListModelMixin which
Provides a .list(request, *args, **kwargs) method, that implements listing a queryset.
So your code will be looking like this:
class User(generics.ListAPIView):
serializer_class = RetrieveLocalSerializer
queryset = User.objects.filter(
fields_1=True,
fields_2=False
)
See http://www.django-rest-framework.org/api-guide/generic-views/#listapiview for more information.
You may either define your queryset in a view or override get_queryset method:
queryset - The queryset that should be used for returning objects from this view. Typically, you must either set this attribute, or override the get_queryset() method. If you are overriding a view method, it is important that you call get_queryset() instead of accessing this property directly, as queryset will get evaluated once, and those results will be cached for all subsequent requests.
You may find more information here: http://www.django-rest-framework.org/api-guide/generic-views/#genericapiview
Hope this will help)

Django REST framework - views serving both regular webpages and API responses depending on the request

I am writing a web site using Django REST framework. This is my first days with the REST, so please bear with me. Basically, the question is,
Can I come up with a class-based view which could serve both as an API for Android developers (with JSON response) and a view rendering regular Django template? Or I have to define two different views for this purpose ?
If the answer to question 1 is that I have to define two separate views, then what is the most DRY method to do that taking into account that the querysets are the same ?
The view:
class TestList(APIView):
renderer_classes = [TemplateHTMLRenderer]
template_name = 'android/test.html'
def get(self, request):
queryset = Test.objects.all()
return Response({'test_qs': queryset})
Put in other words, imagine that I have a model queryset, I want to both render this on my site to my end user, and to send it to my Android developers. What is the best practice in terms of REST framework code architecture? Two different class based views? Or one view with two methods inside it ? Or one view with one magic method which would do both jobs for me ?
I would suggest to keep it separate. With simple CRUD - you will not have the issues with DRY because they are simply different views, consider:
DRF (basically this is all for CRUD, if you want only a list use: ListModelMixin):
class ServiceViewSet(viewsets.ModelViewSet):
queryset = Service.objects.all()
serializer_class = ServiceSerializer
I think that merging this into one View - sooner or later will get you into troubles.
What kind of troubles?
Your templates can at some point use much more data to display the page to the user than your REST API (simple example: current time) - you will start to implement different context for template and different for REST;
and nothing more came to my mind ;) but I have a feeling that two separate views make it much more clean.
I also understand the risk of repeating the same code twice - but you can always extract the repeating code to some helping structures.
As for queryset - if it's simple - do not bother with one place for storing it. If it can get complicated - again - there's no problem to store the queryset in some helping structure and use in both views:
class ProjectQuerysets(object):
my_test_qs = Test.objects.filter(created_at__gte=now-timedelta(seconds=30)).all()
or event something more sophisticated:
class TestQSMixni(object):
def get_queryset(self, *args, **kwargs):
return Test.objects.filter(user=self.request.user) # something like that;
And later:
class TestList(TestQSMixin, APIView):
def get(self, *args, **kwargs):
queryset = self.get_queryset()
# and in REST:
class ServiceViewSet(TestQSMixin, viewsets.ModelViewSet):
serializer_class = ServicesSerializer
# no queryset needed here
(sorry for example with this services, but I have this in some notes :))
Hope this will help you a little.
At the end - it all depends on your needs :) And the project requirements.
Happy coding.