How can I improve pagination performance in multiple queryset django? - django

I have 2 queryset :
queryset_primary = PrimaryUserSerializer(FileUpload.objects.all().order_by('name'), many=True, context=context).data
queryset_secondary = MemberSerializer(Members.objects.all().order_by('member_name'), many=True, context=context).data
Both having different keys ..so that I iterated both querysets :
response = []
for primary in queryset_primary:
# name_pri=primary['primary_user_id']
new_data={
'user_id' : primary['primary_user_id'],
'name': primary['name'],
}
response.append(new_data)
for secondary in queryset_secondary:
new_data={
'user_id' : secondary['secondary_user_id'],
'name': secondary['member_name'],
}
Again I used a common serializer having similar keys in it, for pagination :
responses = self.paginate_queryset(response)
if responses is not None:
serializer = CommonUserSerializer(responses,many=True)
data = {
'code': 200,
'status': "OK",
}
page_nated_data = self.get_paginated_response(serializer.data).data
data.update(page_nated_data)
data['response'] = data.pop('results')
return Response(data)
It totally taking 8 seconds of loading time.
How can I reduce the API loading time ?

When exposing bulk data, models and serializers should be avoided where possible. Have a look at Django's ORM .values() option to make a specific select. Try to construct your own structures for json serialization. I'd also avoid embedding nested data in your structures (especially in list views). It might turn out much cheaper to have a client call twice or filter your api then returning 'complete' data at once.
Have you tried to profile the specific api call? Django's debug toolbar is a convenient tool to pin point the bottleneck.

Have a look at .values() in the ORM:
https://docs.djangoproject.com/en/3.0/ref/models/querysets/#values
Members.objects.all().order_by('member_name').values()
This should give you a QuerySet that returns dictionaries, rather than model instances, when used as an iterable.
The you could couple this with:
from django.http import JsonResponse
def some_view(request):
data = list(Members.objects.all().order_by('member_name').values()) # Use list(), because QuerySet is not JSON serializable by default!
return JsonResponse(data, safe=False)
More specific to your question:
from rest_framework import status
from rest_framework.response import Response
def some_view(request):
merged = []
merged.append(
list(Members.objects.all().order_by('member_name').values('user_id'. 'name')) + list(FileUpload.objects.all().order_by('name').values('user_id'. 'name'))
)
responses = self.paginate_queryset(merged)
if responses is not None:
serializer = CommonUserSerializer(responses, many=True)
data = {
'success': true,
}
paginated_data = self.get_paginated_response(serializer.data).data
data.update(paginated_data)
data['response'] = data.pop('results')
return Response(data, status=status.HTTP_200_OK)
You will also need to do some annotations() to get your user_id attributes to primary_user_id and secondary_user_id
Much more efficient!

Related

How to Make use of Pagination in this API using Django

Here I am trying to create a getData API using Django Rest Framework in which i want to get data using Pagination, i had created this statically but it should be like (getting PAGE and number of ROWS on that page) in request and accordingly data get fetch from database and also show the number entries i got.
please help me out to solve this, i have no idea about how pagination works logically just have basic understanding.
class DeviceControlPolicyView(APIView):
def get(self, request):
if request.data.get('page', 'rows'):
if request.data.get('page') == "1" and request.data.get('rows') == "1":
print(request.data.get('rows'))
print(request.data.get('page'))
qry = DeviceControlPolicy.objects.all()[0:1]
serializer = DeviceControlPolicySerializer(qry, many=True).data
entries = 1
data = {
'details':serializer,
'entry':entries
}
return Response(data)
elif request.data.get('page') == "1" and request.data.get('rows') == "2":
print(request.data.get('rows'))
print(request.data.get('page'))
qry = DeviceControlPolicy.objects.all()[0:2]
serializer = DeviceControlPolicySerializer(qry, many=True).data
entries = 2
data = {
'details': serializer,
'entry': entries
}
return Response(data)
Have a look at DRF's pagination documentation here.
You can set the pagination class in the APIView and have DRF take care of paginating responses from the queryset.
In your case try
from rest_framework import generics
from rest_framework.pagination import PageNumberPagination
class CustomPageNumberPagination(PageNumberPagination):
page_size = 100
page_size_query_param = 'rows'
max_page_size = 1000
class DeviceControlPolicyView(generics.ListAPIView):
queryset = DeviceControlPolicy.objects.all()
serializer_class = DeviceControlPolicySerializer
pagination_class = CustomPageNumberPagination
also refer DRF generics

Django, updating DB column, rest_framework

A bit stuck at updating column in DB. I am sending put request to update a column. But an error returns.
assert isinstance(response, HttpResponseBase), ( AssertionError:
Expected a Response, HttpResponse or HttpStreamingResponse to be
returned from the view, but received a <class 'NoneType'>
here is the front end. sending request.
updateInvoceProject(id, company_name){
return axios.put(API_HANDLER.database_api + "api/v1/update-project", {id, company_name})
},
serializer.py
class InvocesSerializer(serializers.ModelSerializer):
class Meta:
model = Invoces
fields = ("__all__")
view
#csrf_exempt
#api_view(["PUT"])
def update(request):
if request.method == "PUT":
serializer = InvocesSerializer(data=request.data)
if serializer.is_valid():
invoce = Invoces.objects.get(id=serializer.data["id"])
invoce.company_name = serializer.data["company_name"]
invoce.save()
return Response(serializer.data)
urls
urlpatterns = [
#
path("api/v1/update-project", invocesView.update, name="update-project"),
#
]
But in the end, the error I mentioned above is popping. Am I missing something here?
This happens because you are not returning anything when the serialized data is not valid.
you can simply make it like this, to make sure that it is going to return something in all cases.
#csrf_exempt
#api_view(["PUT"])
def update(request):
# if request.method == "PUT":
# You don't have to check for method, since you already defined it
# in api_view(...) decorator.
serializer = InvocesSerializer(data=request.data)
# Raises a ValidatinException which will be sent as a 400 response.
serializer.is_valid(raise_exception=True)
invoce = Invoces.objects.get(id=serializer.data["id"])
invoce.company_name = serializer.data["company_name"]
invoce.save()
return Response(serializer.data)
Better Solution
I suggest that you use DRF's UpdateAPIView (Do it the DRF way :D), in order to avoid getting into such errors, and also to avoid doing all the validation, and serialization by hand.
like the following:
1. Keep your InvocesSerializer, and create another one just for Updating company_name
# Inside serializers.py
class InvocesCompanyNameUpdateSerializer(serializers.ModelSerializer):
def to_representation(self, instance):
return InvocesSerializer(instance).to_representation(instance)
class Meta:
model = Invoces
fields = ('company_name',)
2 . Create an UpdateAPIView class for that serializer
# Inside views.py
class InvoiceUpdateCompanyNameAPIView(UpdateAPIView):
http_method_names = ['put'] # This is only to allow PUT method on this view.
serializer_class = InvocesCompanyNameUpdateSerializer
queryset = Invoces.objects.all()
3 . Now append that view with a re_path to your urls.
# Inside urls.py
urlpatterns = [
#
# you have to add "pk" url path variable, so DRF use it internally to identify
# which object you want to update.
re_path(r"api/v1/update-project/(?P<pk>[\d]+)/",
invocesView.InvoiceUpdateCompanyNameAPIView.as_view(),
name="update-project"),
#
]

adding metadata to filtering in django rest framework

I have a generic ListCreateAPIView view. I've implemented a get_queryset function that performs a search. The function parses the query, extract tags and terms and returns a query set.
def get_queryset(self):
query = self.request.QUERY_PARAMS.get('query', None)
# No deleted items
queryset = Items.objects.filter(deleted__isnull=True)
if query is None:
return queryset
predicates = []
# Generate predicates from query
queryset = queryset.filter(reduce(__and__,predicates))
return queryset
What is the best way to add metadata to the response with data from the get_queryset function ?
I'm looking for something similar to the way pagination works.
{
query : {
terms : ['term1','term2'],
tags : ['tag1','tag2'] ,
}
results : [
{ name : 'item1', .... }
{ name : 'item2', .... }
]
}
EDIT
So i created a custom FilterBackend for the filtering and I now have an instance of the request and the response. Looking at the pagination code for django rest i see it's wrapping the results in serializer. The pagination is build into the view class so the fw invokes the serialization if a paginator is detected. Looking at the search api did not produce any new ideas.
My question remains, What is the best, and least intrusive way, of adding metadata from the filter backend to the response ?
One way i can think of (and one that i don't like) is to overload the matadata onto the request in the filter backend and override finalize_response in the view - without a doubt the worst way to do it.
I'm not sure it's the best way, but I would probably override get to simply intercept the response object and modify response.data however you wish. Something as simple as
from rest_framework import generics
class SomeModelList(generics.ListCreateAPIView):
"""
API endpoint representing a list of some things.
"""
model = SomeModel
serializer_class = SomeModelSerializer
def get(self, request, *args, **kwargs):
response = super(SomeModelList, self).get(request, *args, **kwargs)
# redefine response.data to include original query params
response.data = {
'query': dict(request.QUERY_PARAMS),
'results': response.data
}
return response
If you found yourself repeating this for multiple list views you could keep yourself DRY using a Mixin and include it in your list API classes:
from rest_framework import generics
from rest_framework.mixins import ListModelMixin
class IncludeQueryListMixin(ListModelMixin):
def list(self, request, *args, **kwargs):
response = super(IncludeQueryListMixin, self).list(request, *args, **kwargs)
# redefine response.data to include original query params
response.data = {
'query': dict(request.QUERY_PARAMS),
'results': response.data
}
return response
class SomeModelList(IncludeQueryListMixin, generics.ListCreateAPIView):
"""
API endpoint representing a list of some things.
"""
model = SomeModel
serializer_class = SomeModelSerializer

Django REST Framework - Filtering

I want to filter multiple fields with multiple queries like this:
api/listings/?subburb=Subburb1, Subburb2&property_type=House,Apartment,Townhouse,Farm .. etc
Are there any built in ways, I looked at django-filters but it seems limited, and I think I would have to do this manually in my api view, but its getting messy, filtering on filters on filters
filtering on filters on filters is not messy it is called chained filters.
And chain filters are necessary because sometime there is going to be property_type some time not:
if property_type:
qs = qs.filter(property_type=property_type)
If you are thinking there is going to be multiple queries then not, it will still executed in one query because queryset are lazy.
Alternatively you can build a dict and pass it just one time:
d = {'property_type:': property_type, 'subburb': subburb}
qs = MyModel.objects.filter(**d)
Complex filters are not out of the box supported by DRF or even by django-filter plugin. For simple cases you can define your own get_queryset method
This is straight from the documentation
def get_queryset(self):
queryset = Purchase.objects.all()
username = self.request.query_params.get('username', None)
if username is not None:
queryset = queryset.filter(purchaser__username=username)
return queryset
However this can quickly become messy if you are supported multiple filters and even some of them complex.
The solution is to define a custom filterBackend class and a ViewSet Mixin. This mixins tells the viewset how to understand a typical filter backend and this backend can understand very complex filters all defined explicitly, including rules when those filters should be applied.
A sample filter backend is like this (I have defined three different filters on different query parameters in the increasing order of complexity:
class SomeFiltersBackend(FiltersBackendBase):
"""
Filter backend class to compliment GenericFilterMixin from utils/mixin.
"""
mapping = {'owner': 'filter_by_owner',
'catness': 'filter_by_catness',
'context': 'filter_by_context'}
def rule(self):
return resolve(self.request.path_info).url_name == 'pet-owners-list'
Straight forward filter on ORM lookups.
def filter_by_catness(self, value):
"""
A simple filter to display owners of pets with high catness, canines excuse.
"""
catness = self.request.query_params.get('catness')
return Q(owner__pet__catness__gt=catness)
def filter_by_owner(self, value):
if value == 'me':
return Q(owner=self.request.user.profile)
elif value.isdigit():
try:
profile = PetOwnerProfile.objects.get(user__id=value)
except PetOwnerProfile.DoesNotExist:
raise ValidationError('Owner does not exist')
return Q(owner=profile)
else:
raise ValidationError('Wrong filter applied with owner')
More complex filters :
def filter_by_context(self, value):
"""
value = {"context_type" : "context_id or context_ids separated by comma"}
"""
import json
try:
context = json.loads(value)
except json.JSONDecodeError as e:
raise ValidationError(e)
context_type, context_ids = context.items()
context_ids = [int(i) for i in context_ids]
if context_type == 'default':
ids = context_ids
else:
ids = Context.get_ids_by_unsupported_contexts(context_type, context_ids)
else:
raise ValidationError('Wrong context type found')
return Q(context_id__in=ids)
To understand fully how this works, you can read up my detailed blogpost : http://iank.it/pluggable-filters-for-django-rest-framework/
All the code is there in a Gist as well : https://gist.github.com/ankitml/fc8f4cf30ff40e19eae6

How to include extra data in the Django serializer response?

I am trying to make a livesearch with Django and jQuery. What I've done is make the javascript ask for some data with the getJSON function, then I've set up a view in Django that returns a JSON response automated by the Django serializer.
And this works well, it returns a json response with the text/javascript content-type. To avoid sending all the data, (alot that i dont need) i did this:
response.write(serializers.serialize("json", soknad_list, fields=('name', 'image', 'genre')))
But for instance, the 'genre' field is a manyToMany field, so is it possible to get the values from genre.all.0 for instance and not just the genre ID?
And the model has a function get_absolute _url, is it possible to include this in the JSON response, if so how?
So i guess my question is, is it possible to include stuff other than the raw field data in the JSON response, if not, how would you go about solving my problem?
Django's JSON serialization is based on simplejson, which you can use directly and extend at will to handle whatever kind of objects. So you mostly have two options here: either manually build a list of dicts with relevant data and pass it to simplejson.dumps() (which by default support strings, lists, dicts and numerics), or write your own json encoder that knows how to serialize your specific dataset. FWIW, here's a (not well tested but worked so far) Django model aware json encoder:
from django.utils import simplejson
from django.utils import datetime_safe
from django.utils.functional import Promise
from django.utils.translation import force_unicode
from django.utils.encoding import smart_unicode
from django.core.serializers.json import DjangoJSONEncoder
class ModelJSONEncoder(DjangoJSONEncoder):
"""
(simplejson) DjangoJSONEncoder subclass that knows how to encode fields.
(adated from django.serializers, which, strangely, didn't
factor out this part of the algorithm)
"""
def handle_field(self, obj, field):
return smart_unicode(getattr(obj, field.name), strings_only=True)
def handle_fk_field(self, obj, field):
related = getattr(obj, field.name)
if related is not None:
if field.rel.field_name == related._meta.pk.name:
# Related to remote object via primary key
related = related._get_pk_val()
else:
# Related to remote object via other field
related = getattr(related, field.rel.field_name)
return smart_unicode(related, strings_only=True)
def handle_m2m_field(self, obj, field):
if field.creates_table:
return [
smart_unicode(related._get_pk_val(), strings_only=True)
for related
in getattr(obj, field.name).iterator()
]
def handle_model(self, obj):
dic = {}
for field in obj._meta.local_fields:
if field.serialize:
if field.rel is None:
dic[field.name] = self.handle_field(obj, field)
else:
dic[field.name] = self.handle_fk_field(obj, field)
for field in obj._meta.many_to_many:
if field.serialize:
dic[field.name] = self.handle_m2m_field(obj, field)
return dic
def default(self, obj):
if isinstance(o, Promise):
return force_unicode(o)
if isinstance(obj, Model):
return self.handle_model(obj)
return super(ModelJSONEncoder, self).default(obj)
HTH
There is a handy django third party app / serializer that will allow you to include extra data. It also allows you to include model relations and exclude a list of fields.
It's available at
http://code.google.com/p/wadofstuff/wiki/DjangoFullSerializers
I found out that the simplest thing was to not use the serializer at all. I dont know why I didnt think of this before, but i just used a generic object list view and changed the mimetype to text/javascript and made a JSON template insted of a html template.
Very simple, and that way i managed to get all the data i wanted into the JSON response. This way you can add everything that you can add to a html template into a JSON response, even paginating.
Example extraction of the view i created:
return object_list(request, queryset = object_list,
template_name = 'search/results.js', template_object_name = 'result',
paginate_by = 12, mimetype = 'text/javascript')