I want to lookup a value, for each record in a queryset, and add that value to the queryset.
I have the following in my view.
class InvoiceViewSet(ModelViewSet):
queryset = Invoice.objects.all()
serializer_class = InvoiceSerializer
def get_queryset(self):
user = self.request.user
customer_id = Customer.objects.only('ref').get(user_id=user.id)
queryset = Invoice.objects.filter(supplier_id=customer_id.ref)
for invoice in queryset:
program = FunderProgramMember.objects.get(supplier=invoice.supplier_id, buyer=invoice.buyer)
invoice.annotate(discount_rate=Value(program.discount_rate))
return queryset
Since the invoices in the queryset could have different discounts, I loop through the queryset and add the relevant discount.
I am receiving the following error: 'Invoice' object has no attribute 'annotate'
I can annotate to the queryset (which doesn't help me since the records in the queryset won't all have the same discount) but it seems I can't annotate to a record in the queryset.
Is there another way of achieving this?
And even if I could annotate to individual record, I am not sure whether those values would be passed with my return queryset?
EDIT:
Not sure whether this is the best way of doing it..but it seems to work:
class InvoiceViewSet(ModelViewSet):
queryset = Invoice.objects.all()
serializer_class = InvoiceSerializer
def get_queryset(self):
user = self.request.user
customer_id = Customer.objects.only('ref').get(user_id=user.id)
queryset = Invoice.objects.filter(supplier_id=customer_id.ref)
for invoice in queryset:
program = FunderProgramMember.objects.get(supplier=invoice.supplier_id, buyer=invoice.buyer)
invoice.discount_rate = program.discount_rate
return queryset
You can use Subquery expressions to support this annotation like this:
program_discount_subquery = FunderProgramMember.objects.filter(
supplier=OuterRef('supplier'), buyer=OuterRef('buyer')
).values('discount_rate')[:1]
queryset = Invoice.objects.filter(
supplier_id=customer_id.ref
).annotate(discount_rate=Subquery(program_discount_subquery))
Each invoice in the queryset will then have an attribute discount_rate.
Related
I have a view that returns all the users that are either working in or are students in the school that the request.user owns, in my model's I have two fields works and learns that are foreignkey fields referencing the school object, when filtering the users to match the school that the request.user owns, I have to filter like something like so:
class AllUserList(generics.ListAPIView):
permission_classes = [IsSchoolOwner, ]
# queryset = User.objects.exclude(type__isnull=True)
serializer_class = ListUsersSerializer
def get_queryset(self):
request = self.request.user
if request.type == User.TYPES.OWNER:
queryset = User.objects.filter(is_active=True,
type__isnull=False, works__in=request.owns.all(), learns__in=request.owns.all())
return queryset
there is an issue here as users that are working in the school will have the learns fields as null and the students will have the works field as null so how can I find if either of the fields matches and if they do make it ignores the other field.
You can specify a condition with a Q object [Django-doc] to define disjunctive conditions:
class AllUserList(generics.ListAPIView):
permission_classes = [IsSchoolOwner, ]
model = User
queryset = User.objects.all()
serializer_class = ListUsersSerializer
def get_queryset(self):
user = self.request.user
qs = super().get_queryset()
if request.type == User.TYPES.OWNER:
qs = qs.filter(
Q(works__in=user.owns.all()) | Q(learns__in=user.owns.all()),
is_active=True, type__isnull=False
)
return qs
For more information, see the Complex lookups with Q objects section of the documentation.
I've an endpoint that accepts either the uuid or the phonenumber of the user
url(r'^(?P<uuid>[0-9A-Fa-f+-]+)/$', view_name.as_view()),
Now, I've a queryset that filter accordingly. Here it is.
class UserDetails(RetrieveUpdateAPIView):
serializer_class = UserSerializer
lookup_field = 'uuid'
def get_queryset(self):
"""
Over-riding queryset.
Filter user based on the user_id(Works for both msisdn & uuid).
"""
msisdn_or_uuid = self.kwargs[self.lookup_field]
queryset = Users.objects
try: # checking if the forwarded param is user_id or msisdn.
UUID(msisdn_or_uuid)
instance = queryset.filter(uuid=msisdn_or_uuid)
except ValueError:
instance = queryset.filter(msisdn=msisdn_or_uuid)
print instance # prints a queryset. But returns 404.
return instance
Now the problem is whenever phone number is passed, it returns 404 not found. But the objects clearly exist.
Is there any setting in DRF that filters on two or more fields simultaneously without over-riding the get_queryset.??
I found a related question, but couldn't make it work. Where am I going wrong?
UPDATE
This is what I've tried. It works. But would like to hear better solutions(if any)
class FilterByUuidMsisdnMixin(object):
"""
Mixin to filter by multiple lookup_fields.
Apply this mixin to any view or viewset to get multiple field (only uuid &
msisdn) filtering, instead of the default single field filtering.
"""
def get_object(self):
"""Over-riding get_object."""
queryset = self.get_queryset() # Get the base queryset
queryset = self.filter_queryset(queryset) # Apply any filter backends
field = self.kwargs.get(self.lookup_field)
filters = {}
try: # checking if the forwarded param is user_id or msisdn.
UUID(field)
filters['uuid'] = field # filter by uuid.
except ValueError:
filters['msisdn'] = field # filter by msisdn.
obj = get_object_or_404(queryset, **filters) # Lookup the object
self.check_object_permissions(self.request, obj) # check permissions.
return obj
So I have typical generic view:
class FooListAPIView(generics.ListAPIView):
serializer_class = FooSerializer
lookup_fields = ('area_id', 'category_id', )
def get_queryset(self):
area = Area.objects.get(pk=self.kwargs.get('area_id'))
area_tree = area.get_tree(parent=area) #returns queryset
category = Category.objects.get(pk=self.kwargs.get('category_id'))
queryset = Foo.objects.filter(area__in=area_tree, category=category)
return queryset
def get_object(self):
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
filter = {}
for field in self.lookup_fields:
filter[field] = self.kwargs[field]
return get_object_or_404(queryset, **filter)
My problem is, if i try get area or category objects, which doesn't exist, browser throws me error:
Area matching query does not exist.
How can I make it so, that when Area matching query does not exist, I get standard rest framework 404 response?
The problem here is that get_queryset doesn't really expect any failures. In your case, although you are returning a queryset, you seem to be hitting the database with the Area.objects.get(pk=self.kwargs.get('area_id')) call. When this fails, it violates the I/O defined by get_queryset which isn't expecting the Area.DoesNotExist exception. So it fails and you end up with a Django 500 error.
What you need to ensure is that the get_queryset method, returns a queryset, preferably without making any calls to the DB (I say preferably, since there is no such rule that says it shouldn't hit the DB, but its generally understood that get_queryset wont be the one to actually perform the DB query). Then if you must, you can freely perform any get operations on your DB inside the get_object with the get_object_or_404 shortcut. Since get_object_or_404 raises an Http404 exception and get_object knows how to handle this exception, it will gracefully return the 404 page that you are expecting.
If you can ensure your area.get_tree implementation can work with a parent queryset, instead of a parent object, then you could do something like this:
class FooListAPIView(generics.ListAPIView):
serializer_class = FooSerializer
lookup_fields = ('area_id', 'category_id', )
def get_queryset(self):
area = Area.objects.filter(pk=self.kwargs.get('area_id'))
area_tree = area.get_tree(parent=area) #returns queryset
category = Category.objects.filter(pk=self.kwargs.get('category_id'))
queryset = Foo.objects.filter(area__in=area_tree, category__in=category)
return queryset
def get_object(self):
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
filter = {}
for field in self.lookup_fields:
filter[field] = self.kwargs[field]
return get_object_or_404(queryset, **filter)
If you are unable to get area.tree to work without a queryset, then you can delay some of your get_queryset logic to get_object. Like so:
from django.http import Http404
class FooListAPIView(generics.ListAPIView):
serializer_class = FooSerializer
lookup_fields = ('area_id', 'category_id', )
queryset = Foo.objects.all()
def get_object(self):
queryset = self.get_queryset()
queryset = self.filter_queryset(queryset)
area = get_object_or_404(Area, **{'pk': self.kwargs.get('area_id')})
area_tree = area.get_tree(parent=area)
category = get_object_or_404(Category, **{'pk': self.kwargs.get('category_id')})
queryset = queryset.filter(area__in=area_tree, category=category)
filter = {}
for field in self.lookup_fields:
filter[field] = self.kwargs[field]
return get_object_or_404(queryset, **filter)
Here's the situation. I got a list on my Django REST API: /playerslist/
It returns me a list of players just like this one:
http://pastebin.com/JYA39gHT
This is exactly what I want for the moment. But now, I need this:
Going for /playerslist/1/ gives me different infos for the Player Number 1. The list is here only for listing players with basic informations. But I need detailed view for players, containing info from other models and with different serialization, it must be a basic issue, but as I'm totally new to Django and Python in general, I must misunderstanding something.
Here is my Viewset:
class PlayersListViewSet(viewsets.ModelViewSet):
queryset = Player.objects.all()
serializer_class = PlayersListSerializer
http_method_names = ['get', 'post']
pagination_class = None
filter_backends = [filters.OrderingFilter]
ordering_fields = ['name']
def get_queryset(self):
queryset = Player.objects.all()
team_id = self.request.query_params.get('team', None)
if team_id:
try:
queryset = queryset.filter(team=team_id)
except ValueError:
raise exceptions.ParseError()
return queryset
How can I achieve this ? Must I use #detail_route to have something like playerslist/1/detail ? I've already tried but DRF's documentation only show a single example and it's not clear at all for me.
You can override the methods retrieve (returning one instance) or list (returning list obviously) as shown in first example in http://www.django-rest-framework.org/api-guide/viewsets/.
class PlayersListViewSet(viewsets.ModelViewSet):
queryset = Player.objects.all()
serializer_class = PlayersListSerializer
http_method_names = ['get', 'post']
pagination_class = None
filter_backends = [filters.OrderingFilter]
ordering_fields = ['name']
def get_queryset(self):
queryset = Player.objects.all()
team_id = self.request.query_params.get('team', None)
if team_id:
try:
queryset = queryset.filter(team=team_id)
except ValueError:
raise exceptions.ParseError()
return queryset
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = PlayerDetailSerializer(instance)
return Response(serializer.data)
Where PlayerDetailSerializer is another serializer with different fields (whatever you need) and there is no need to specify it in serializer_class.
To get different results when you do a 'detail' view, you want to change the serializer when doing a 'retrieve' call. I've done this with a custom mixin for a ModelViewSet, which expects a special "detail_serializer_class":
class DifferentDetailSerializerMixin(object):
"""
For a viewset, mix this in to use a different serializer class
for individual 'retrieve' views, different from the standard
serializer for lists.
"""
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.detail_serializer_class(instance, context=self.get_serializer_context())
return Response(serializer.data)
Your viewset is simply:
class PlayersListViewSet(DifferentDetailSerializerMixin, viewsets.ModelViewSet):
queryset = Player.objects.all()
serializer_class = PlayersListSerializer
detail_serializer_class = PlayersDetailSerializer
filter_backends = [filters.OrderingFilter]
ordering_fields = ['name']
Here, PlayersDetailSerializer is another Serializer that has more fields that you want to return.
As an aside, if you want to support optional filtering by teams, I would strongly recommend using django-filter. That way you don't have to worry about validation etc. Once installed, it's simply a case of adding this to your viewset:
filter_backends = (filters.OrderingFilter, filters.DjangoFilterBackend, )
filter_fields = ['team']
See the docs for more info.
I have a serializer that has a methodfield popular such as it returns a specific float. Is there a way to order by the serializermethodfield?
Code Snippets:
serializers.py
class PostListSerializer(serializers.HyperlinkedModelSerializer):
popular = serializers.SerializerMethodField('popularity')
def popularity(self, obj):
time = datetime.datetime.now()
n = obj.created.replace(tzinfo=None)
r = time.replace(tzinfo=None)
x = r - n
a = float(str(x.total_seconds()//(60*60*24)))
view = obj.post.count()
return view / a
views.py
class PopularPostsSet(viewsets.ModelViewSet):
queryset = Post.objects.all()
serializer_class = PostListSerializer
filter_backends = (filters.OrderingFilter,)
ordering_fields = ('popular')
Filter backends are based on changing the queryset. So they only work for fields in the database, not calculated fields.
So you I see two possibilities:
put the popularity into the database, update it when saving the Post or other data changes that the number depends on.
sort the result yourself. Just override list in the viewset, read the ordering-argument yourself and sort the results you get from calling list from your parent.