I am overriding the get_queryset function in serializers.PrimaryKeyRelatedField to something like this achieve what I described in the title:
def get_queryset(self):
queryset = self.queryset
if self.root.instance:
return queryset.filter(**{str(self.root.instance._meta.verbose_name_plural) :
self.root.instance})
else:
return queryset
This feels like an extremely hacky way to achieve it. Is there a cleaner syntax for getting related objects given a queryset and a root instance? I want the function to be generic so I can use it elsewhere. Or is there a rest framework's recommended way of setting queryset?
Related
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!
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)
I am trying to understand Django's class based views (very new to it), especially, ListView. I am struggling to understand where the "business logic should go". Say for example, I have the following class:
#views.py
class DisplayListView(ListView):
model = Cars
template_name = "searchres_list.html"
paginate_by = '5'
context_object_name = "titles"
def get_context_data(self, **kwargs):
context = super(SearchDisplayListView, self).get_context_data(**kwargs)
# custom logic whoch spits out "now". in this example [1 -->10]
context['now'] = [1,2,3,4,5,6,7,8,9,10]
return context
It works fine and I am able to look the [1 --> 10] on my template. However, when I look at the methods available under ListView I see that I could probably include my logic in get_queryset method. So, something like:
def get_queryset(self):
# Fetch the queryset from the parent get_queryset
queryset = super(SearchDisplayListView, self).get_queryset()
# custom logic whoch spits out "now". in this example [1 -->10]
queryset = [1,2,3,4,5,6,7,8,9,10]
return queryset
So, my rather (stupid) question is (or have I got this all completely wrong!), where should the business logic ideally go:
def get_context_data
def get_queryset
Thanks for your time.
Probably the best answer to such a subjective question will be: it depends.
My personal algorithm for dealing with the situations like this is the following:
if you need to add something to the context that will be passed to the template, then you don't have a choice actually, because in get_queryset method you can only modify the queryset for your ListView. So I use get_context_data in this case.
but if you're going to perform some dynamic queryset modifications, let's say your view can operate on similar model classes and the actual class is determined by the arguments passed into the view, then probably you need to overwrite get_queryset method.
Hope I gave you some insights on the topic :)
I'm trying to create a custom function that I can place in a queryset "chain" that will apply a filter to the queryset. Like with normal Django queryset filters, it will take the queryset to the left of it and pass the resulting queryset to the right.
Before adding my custom function to the queryset chain:
models.MyModel.objects.all()
models.MyModel.objects.some_manger_function()
models.MyModel.objects.some_manger_function().count()
models.MyModel.objects.some_manger_function().filter(title='something')
After adding my custom function to the queryset chain:
models.MyModel.objects.all().my_custom_filter()
models.MyModel.objects.some_manger_function().my_custom_filter()
models.MyModel.objects.some_manger_function().my_custom_filter().count()
models.MyModel.objects.some_manger_function().my_custom_filter()\
.filter(title='something')
I'm not sure how to construct my function to do this. Does it need some sort of decorator around it?
???? def my_custom_filter(???):
???? return qs.filter(id__gte=10)
Does anyone know a good way to accomplish this?
The following might work, but I was hoping for something a little more Django-like.
def my_custom_filter(qs):
return qs.filter(id__gte=1)
my_custom_filter(models.MyModel.objects.all()).count()
Any advice is much appreciated.
Thanks,
Joe
UPDATE: I'm trying to work out the details of Ignacio's solution. I've not done too much with QuerySet overriding so I'm piecing together what I'm able to find...
class MyQuerySet(QuerySet):
def filter(self, *args, **kwargs):
return super(self.__class__, self).filter(*args, **kwargs).\
filter(id__gt=5)
class MyManager(models.Manager):
def testqs(self):
return MyQuerySet(self.model)
However, I don't think this is working the way I expect. Any suggestions?
>>> models.MyModel.objects.testqs().filter()
UPDATE 2:
This article proved to be useful.
http://zmsmith.com/2010/04/using-custom-django-querysets/
You will need to write your own QuerySet child class with the method added, then use that class in the manager.
I am writing a custom manager, and implementing the get_query_set method.
Basically, I want to see that certain fields are passed into the query, think the Custom Site Manager but not wanting to add filters I want to ensure that some fields ARE filtered on. Below is one way, but I was wondering if there is a better way to get the fields that will be constrained
class OrgBaseModelManager(models.Manager):
def get_query_set(self):
qs = super(OrgBaseModelManager, self).get_query_set()
#Below returns a list of
constraint_lists = [c.children for c in qs.query.where.children]
import itertools
chain = itertools.chain(*constraint_lists)
constraint_fields = list(chain)
#here is where I would do my magic
return super(OrgBaseModelManager, self).get_query_set()
So my question is, to see if there is a better approach, I am concerned for very convoluted queries, that I have not flattend the tree properly.
Any ideas of better approaches?