I'm trying to serialize more than one queryset, but I noticed that only one of them becomes serialized. This is the approach I'm currently trying.
class GetDetails(APIView):
def get(self, request):
todays_date = time.strftime('%Y-%m-%d')
#Get results according to specified criteria
queryset = People.objects.filter(date = todays_date, assigned_to = 1)
#Check if any querysets are available
if queryset:
#Iterate through each queryset, serialize and return a response
for person in queryset:
serializer=ASerializer(person)
return Response(serializer.data)
else:
return Response({'TODO':'TODO'})
First of all the way you are performing queryset is not right, you are iterating over queryset with person variable and you are not using person variable at all.
And for the question, use many=True attribute of serializer. It will create a list of serialized items for you.
like this:
class GetDetails(APIView):
def get(self, request):
todays_date = time.strftime('%Y-%m-%d')
queryset = People.objects.filter(date = todays_date, assigned_to = 1)
return Response(ASerializer(many=True).to_representation(queryset))
Maybe your code should be like this:
def get(self, request):
todays_date = time.strftime('%Y-%m-%d')
queryset = People.objects.filter(date = todays_date, assigned_to = 1)
if queryset.exists(): # Lazy check if entry Exists.
serializer=ASerializer(queryset, many=True)
return Response(serializer.data)
else:
return Response({'TODO':'TODO'})
Related
I have a problem with my PATCH request instance. Currently, data that is being PATCHED and sent from a request is overriding every item in my list of strings ArrayField inside my model object.
I need my patch request behavior to append to the rest of the items in ArrayField object, not delete/override.
How can I go about doing that?
I assume I need to override the patch method within RetrieveUpdateAPIView,
so I've started out here:
def patch(self, request, **kwargs):
item = self.kwargs.get('slug')
serializer = StockListSerializer(item, data=request.data, partial=True)
if serializer.is_valid():
serializer.save()
Response(serializer, status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer.py:
class StringArrayField(serializers.ListField):
def to_representation(self, obj):
obj = super().to_representation(obj)
return ",".join([str(element) for element in obj])
def to_internal_value(self, data):
data = data.split(",")
return super().to_internal_value(data)
class StockListSerializer(serializers.ModelSerializer):
stock_list = StringArrayField()
class Meta:
model = Bucket
fields = ("stock_list",)
view.py
class EditBucketSymbols(generics.RetrieveUpdateAPIView):
permission_classes = [IsAuthenticated]
serializer_class = StockListSerializer
queryset = Bucket.objects.all()
def get_object(self, queryset=queryset, **kwargs):
item = self.kwargs.get('slug')
return get_object_or_404(Bucket, slug=item)
url.py:
path('bucket/symbols/<str:slug>/', EditBucketSymbols.as_view(), name='editsymbols')
model.py:
stock_list = ArrayField(models.CharField(max_length=6,null=True),size=30,null=True, blank=True)
You can make use of the field-lever validation technique here,
class StockListSerializer(serializers.ModelSerializer):
stock_list = StringArrayField()
class Meta:
model = Bucket
fields = ("stock_list",)
def validate_stock_list(self, stock_list):
existing_stock_list = []
if self.instance and self.instance.stock_list:
# Patch or Put request
existing_stock_list = self.instance.stock_list
return existing_stock_list + stock_list
I would do this within the .validate() method of the serializer. something like:
def validate(self, validated_data):
# Make sure that this only runs for Patch requests
if self.context["request"].method == "PATCH"
# Get list of existing stocks from instance and append incoming list
stock_list = self.instance.stock_list + validated_data["stock_list"]
# Replace data
validated_data["stock_list"] = stock_list
return validated_data
I want to show the pagination feature in my API and I am using APIView with multiple serializers.
I know it is very easy to show pagination with ListView.
I have seen somewhere that combining ListModelMixin and APIView works but if my code is as follows:
class ListModelMixin(object):
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serilaizer.data)
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
def get(self, request, format=None):
"""
Return a list of all devices of this user.
"""
reply = {}
try:
products = BaseItem.objects.owned_items().filter(owner=request.user)
reply['data'] = OwnedItemSerializer(products, many=True).data
items = BaseItem.objects.dev_items().filter(owner=request.user)
reply['data'].extend(ItemSerializer(items, many=True).data)
except:
reply['data'] = []
return Response(reply, status.HTTP_200_OK)
How can I combine them so I can get paginated results?
Thanks in advance!
First things first, what you are currently doing is too complex without reason.
In order to achieve a "paginatable" queryset, it is preferred to change your owned_items() and dev_items() in simple filter combinations, rather than model methods. To clarify by example:
products = BaseItem.objects.filter(owner=request.user, owned=True)
instead of
products = BaseItem.objects.owned_items().filter(owner=request.user)
That way, you can produce a single queryset which will be easier to paginate:
user_items = BaseItem.objects.filter(
Q(owner=request.user, owned=True) |
Q(owner=request.user, dev=True)
)
Note 1: You can simplify things further if you like, but that gets out of scope of your question. As food for thought, check this out:
user_items = BaseItem.objects.filter(owner=request.user).distinct()
Note 2: You should use a single serializer for a single model because what you are doing adds complexity without benefit (high risk-low reward situation)
With the above mentioned and assumed:
There are some ways to achieve what you want here:
By utilizing GeneriAPIView and ListModelMixin you can refactor your class in such a way to have a .list() method with auto-pagination:
from rest_framework import mixins, generics
class ItemsAPIView(mixins.ListModelMixin, generics.GenericAPIView,):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
serializer_class = OwnedItemSerializer
# You can define .get in here if you really need it.
# You can also override .list to add specific functionality
If you don't want to use the above, and you want to keep your APIView, then you can keep your get method and provide pagination for it as mentioned in this Q&A example:
class ItemsAPIView(APIView):
permission_classes = (permissions.IsAuthenticated,)
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS
serializer_class = MyNewUnifiedSerializerClass
def get(self, request):
user_items = BaseItem.objects.filter(
owner=request.user
).distinct()
page = self.paginate_queryset(user_items)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(user_items, many=True)
return Response(serializer.data)
# Now add the pagination handlers taken from
# django-rest-framework/rest_framework/generics.py
#property
def paginator(self):
"""
The paginator instance associated with the view, or `None`.
"""
if not hasattr(self, '_paginator'):
if self.pagination_class is None:
self._paginator = None
else:
self._paginator = self.pagination_class()
return self._paginator
def paginate_queryset(self, queryset):
"""
Return a single page of results,
or `None` if pagination is disabled.
"""
if self.paginator is None:
return None
return self.paginator.paginate_queryset(
queryset,
self.request,
view=self
)
def get_paginated_response(self, data):
"""
Return a paginated style `Response` object
for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data)
I am using django rest framework and instead of getting the complete list of an object, I only want to get a specific value, like max(date) for example. Here is the code I am using:
My Serializer
class MoodSerializer(serializers.ModelSerializer):
class Meta:
model = Mood
fields = ('date', 'rating')
def create(self, validated_data):
return Mood.objects.create(**validated_data)
My View
class MoodList(generics.ListCreateAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
class MoodDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
My URLS
url(r'^mood/$', views.MoodList.as_view()),
url(r'^mood/(?P<pk>[0-9]+)/$', views.MoodDetail.as_view()),
So if fire a GET on "max_mood" I want the latest Mood entry from the db.
Instead of Mood.objects.all(), why not sort Mood objects by some_criteria and get the first one (which will be the max one). Like this:
mood = Mood.objects.order_by('-some_criteria').first()
There is another way but it requires additional queries:
from django.db.models import Max
maximum = Mood.objects.aggregate(m=Max('some_criteria')).get('m')
max_mood = Mood.objects.get(some_criteria=maximum)
view
class MaxMoodView(generics.RetrieveAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
def retrieve(self, request, *args, **kwargs):
mood=self.get_queryset().order_by('-date').first()
return Response(self.get_serializer(instance=mood))
urls
url(r'^mood/max_mood/$', views.MaxMoodView.as_view()),
Alright I got it working with the help of the comments:
view:
class MaxMoodView(generics.ListCreateAPIView):
queryset = Mood.objects.all()
serializer_class = MoodSerializer
def get(self, request, format=None):
mood=self.get_queryset().order_by('-date').first()
serializer = MoodSerializer(mood)
return Response(serializer.data)
urls:
url(r'^mood/max_mood/$', views.MaxMoodView.as_view()),
So I have this queryset:
from django.contrib.gis.db.models.query import GeoQuerySet
from django.db import models as base_models
class RestaurantsQuerySet(GeoQuerySet):
def get_list(self, lng, lat):
reference_point = Point(lng, lat, srid=SRID)
return self.annotate(rating=models.Avg('comments__rating'))\
.annotate(distance=Distance('location', reference_point))
def count(self):
return self.values('id').aggregate(count=base_models.Count('id'))['count']
I thought that the query would look something like:
SELECT COUNT("__col1")
FROM (
SELECT "restaurants_restaurant"."id" AS "__col1"
FROM "restaurants_restaurant"
GROUP BY "restaurants_restaurant"."id") subquery
And instead django orm creates this little monstrosity:
SELECT COUNT("__col1")
FROM (
SELECT "restaurants_restaurant"."id" AS Col1, "restaurants_restaurant"."id" AS "__col1"
FROM "restaurants_restaurant"
LEFT OUTER JOIN "comments_comment" ON ("restaurants_restaurant"."id" = "comments_comment"."restaurant_id")
GROUP BY "restaurants_restaurant"."id", ST_Distance_Sphere("restaurants_restaurant"."location",
ST_GeomFromEWKB('\x0101000020e61000003eb555a41d2d4b405a338d81d0a73240'::bytea
))) subquery
First method to be called is get_list. It looks as if django would "remember" that call and that the qs was annotated with rating and distance and places it also into the count query. So I guess the question is - how do I "reset" this queryset to the state before annotating it?
EDIT:
Okay, seems my question was not complete. I also have a RestaurantsList view defined as follows:
class RestaurantList(generics.ListAPIView):
def get_queryset(self):
return Restaurant.objects.get_list(self._lng, self._lat)
I took a look into the entrails of django-rest-framework and I can see this:
class ListModelMixin(object):
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
queryset = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data)
serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)
So it looks like it always uses the queryset that was returned from the get_queryset method and since it's annotated with distance and rating it ends up being included in the count query. Still no solution to this...
After browsing through django-rest-framework code I came up with this idea:
Override default Paginator class for a given view:
class RestaurantList(generics.ListAPIView):
pagination_class = custom.LimitOffsetPagination
Create custom pagination class:
class LimitOffsetPagination(pagination.LimitOffsetPagination):
def __init__(self):
self._countable_queryset = None
self._was_counted = False
def was_initialized(self):
return self._countable_queryset is not None
def set_raw_queryset_for_count(self, queryset: QuerySet):
self._countable_queryset = queryset
def paginate_queryset(self, queryset, request, view=None):
self.limit = self.get_limit(request)
if self.limit is None:
return None
self.offset = self.get_offset(request)
self.count = self._countable_queryset.count()
self.request = request
if self.count > self.limit and self.template is not None:
self.display_page_controls = True
return list(queryset[self.offset:self.offset + self.limit])
Override views paginator property:
#property
def paginator(self):
paginator = super().paginator
if not paginator.was_initialized():
paginator.set_raw_queryset_for_count(Restaurant.objects.all())
return paginator
Now the count query will look a bit more friendly
SELECT COUNT(*) AS "__count" FROM "restaurants_restaurant"
I have a slightly complicated APIView which makes that I can't use a generic ListAPIView to return a queryset. But I can't seem to simply serialize a simple Django queryset using a ModelSerializer, even when I set many=True.
Somehow this doesn't work:
serializers.py:
class SomeModelSerializer(serializers.ModelSerializer):
class Meta:
model = SomeModel
fields = ['some_field']
views.py:
from rest_framework.response import Response
class SomeAPIView(APIView):
serializer_class = SomeInputSerializer
def post(self, request, format=None):
serializer = self.serializer_class(data=request.data)
if serializer.is_valid():
# first some business logic, then return results
results = SomeModel.objects.all()
output_serializer = SomeModelSerializer(results, many=True)
return Response(output_serializer.data)
All I keep getting is: 'ListSerializer' object is not iterable.
What am I doing wrong?
Error:
/projectfolder/venv/lib/python2.7/site-packages/django/template/defaulttags.py in render
try:
values = self.sequence.resolve(context, True)
except VariableDoesNotExist:
values = []
if values is None:
values = []
if not hasattr(values, '__len__'):
values = list(values) ...
len_values = len(values)
if len_values < 1:
return self.nodelist_empty.render(context)
nodelist = []
if self.is_reversed:
values = reversed(values)
values = list(values) seems to be responsible for the error
Was running into the same problem as you did. I found a quick and simple fix for the error: Copy the serializer data to a new array and return that.
results = SomeModel.objects.all()
output_serializer = SomeModelSerializer(results, many=True)
data = output_serializer.data[:]
return Response(data)
This works for me, hopefully for you as well.
Below works for me using an as_view() url:
class ListCreateMemberViewSet(generics.ListCreateAPIView):
"""
API endpoint that allows multiple members to be created.
"""
queryset = Member.objects.none()
serializer_class = MemberSerializer
def get_queryset(self):
queryset = Member.objects.all()
return queryset
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data, many=isinstance(request.data, list))
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
results = Member.objects.all()
output_serializer = MemberSerializer(results, many=True)
data = output_serializer.data[:]
return Response(data)
The error is a result of trying to run a list on a serializer directly.
For example, this would raise the above exception:
results = Member.objects.all()
output_serializer = MemberSerializer(results, many=True)
all_results = list(output_serializer) # this line here
all_results += output_serializer # or this line here
And this would not (difference is that we are listing output_serializer.data instead of output_serializer):
results = Member.objects.all()
output_serializer = MemberSerializer(results, many=True)
all_results = list(output_serializer.data)
all_results += output_serializer.data
I have the feeling that in the original question, the example code was not 100% matching the actual code.