I don't want to run count queries on views where count is not needed.
How can I turn it off?
I found the following workaround in another post on stackoverflow.
The count query is not fired, but I can see pages that have no data.
(I can see up to ?page=10000, even though there are only about 10 pages.)
#settings.py
REST_FRAMEWORK = {
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
"PAGE_SIZE": 20,
...
}
import sys
from django.core.paginator import Paginator
from django.utils.functional import cached_property
from rest_framework.pagination import PageNumberPagination
class CustomPaginatorClass(Paginator):
#cached_property
def count(self):
return sys.maxsize
class CustomPagination(PageNumberPagination):
django_paginator_class = CustomPaginatorClass
You can raise a HTTP 404 in case the page does not contain any elements with:
from rest_framework.exceptions import NotFound
class CustomPagination(PageNumberPagination):
django_paginator_class = CustomPaginatorClass
def paginate_queryset(self, queryset, request, view=None):
data = super().paginate_queryset(queryset, request, view=view)
if not data:
raise NotFound('No data found for this page')
return data
This will fetch the paginated data with one query, and then we check if there is at least one element. If that is not the case, we know that the page we are using should not exist.
Related
I'm building a system where I store a Member model on a Django server, one of the attributes of Member is score which is what I want to change using API calls. My question is what would be the best way to do this? I looked into the Django REST framework but it seems a bit overkill for what I'm trying to do. I've been trying to pass the necessary information through the url using regular expressions but I'm unsure if it will work. Outline of what I need is below
iOS/Android app makes request sending pk and score to add to total
server updates appropriate model instance and returns True/False to app depending if save was successful
You can achieve this by this quite dirty solution:
urls.py
from django.urls import path
from . import views
urlpatterns = [
path('<int:member_id>/<int:score_to_add>/', views.update_score, name='update_score'),
]
views.py
from django.http import HttpResponse
from .models import Member
def update_score(request, member_id, score_to_add):
member = Member.objects.get(pk=member_id)
member.score += score_to_add
try:
member.save
return HttpResponse("True")
except:
return HttpResponse("False")
Also you can respond with Json. Here is alternative views:
Alternative views.py
from django.http import JsonResponse
from .models import Member
def update_score(request, member_id, score_to_add):
member = Member.objects.get(pk=member_id)
member.score += score_to_add
try:
member.save
return JsonResponse({'status': True})
except:
return JsonResponse({'status': False})
But i think Django Rest Framework is a better way to do this.
You can create a view to return JsonResponse. Take example of polls app in django and convert a post view to return a JSON response.
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.http import JsonResponse
from .models import Choice, Question
# Need to disable csrf here
#csrf_exempt
def vote(request, question_id):
question = get_object_or_404(Question, pk=question_id)
# If you want Json requests Or you can use
# received_json_data=request.POST if you are using normal form data request.
received_json_data=json.loads(request.body)
try:
selected_choice = question.choice_set.get(pk=received_json_data['choice'])
except (KeyError, Choice.DoesNotExist):
# If failed
return JsonResponse({'result': False})
else:
selected_choice.votes += 1
selected_choice.save()
return JsonResponse({'result': True})
Although It works but I would still recommend using DRF because what you are doing needs proper REST API and DRF eases a lot of pain points.
I want to get some view cached depending on the POST data sent to it.
Does the django.views.decorators.cache.cache_page decorator do that automatically or do I need to tweak it somehow? In the latter case, how should I do that?
I'm trying to cache GraphQL POST requests.
No, POST responses are never cached:
if request.method not in ('GET', 'HEAD'):
request._cache_update_cache = False
return None # Don't bother checking the cache.
(from FetchFromCacheMiddleware in django.middleware.cache).
You'll have to implement something yourself using the low-level cache API. It's most unusual to cache a response to a POST request, since a POST request is meant to change things in the database and the result is always unique to the particular request. You'll have to think about what exactly you want to cache.
I ended up creating a custom decorator which caches responses based on request path, query parameters, and posted data:
# myproject/apps/core/caching.py
import hashlib
import base64
from functools import wraps
from django.core.cache import cache
from django.conf import settings
def make_hash_sha256(o):
hasher = hashlib.sha256()
hasher.update(repr(make_hashable(o)).encode())
return base64.b64encode(hasher.digest()).decode()
def make_hashable(o):
if isinstance(o, (tuple, list)):
return tuple((make_hashable(e) for e in o))
if isinstance(o, dict):
return tuple(sorted((k,make_hashable(v)) for k,v in o.items()))
if isinstance(o, (set, frozenset)):
return tuple(sorted(make_hashable(e) for e in o))
return o
def cache_get_and_post_requests(duration=600):
def view_decorator(view):
#wraps(view)
def view_wrapper(request, *args, **kwargs):
# TODO: make the key also dependable on the user or cookies if necessary
cache_key = "-".join((
settings.CACHE_MIDDLEWARE_KEY_PREFIX,
make_hash_sha256((
request.path,
list(request.GET.items()),
list(request.POST.items()),
request.body,
)),
))
cached_response = cache.get(cache_key)
if cached_response:
return cached_response
response = view(request, *args, **kwargs)
cache.set(cache_key, response, duration)
return response
return view_wrapper
return view_decorator
Then I can use it in the URL configuration like so:
# myproject/urls.py
from django.urls import path
from django.conf.urls.i18n import i18n_patterns
from graphene_django.views import GraphQLView
from myproject.apps.core.caching import cache_get_and_post_requests
urlpatterns = i18n_patterns(
# …
path("graphql/", cache_get_and_post_requests(60*5)(GraphQLView.as_view(graphiql=True))),
)
I have a very basic Django Rest API.
I don't know how to have some HTML views, in the same django project, which uses API (finally keeping API returning JSON only).
I followed this, but it seems to change the API View (in this case, curl will retrieve HTML and not JSON) :
https://www.django-rest-framework.org/api-guide/renderers/#templatehtmlrenderer
Do I need another Django App ? Another Django project ? Some JS ?
EDIT :
Ok, I've seen it's possible, thanks to rrebase.
But I can't retrieve the JSON with Curl, here my views.py
from rest_framework import generics
from rest_framework.renderers import TemplateHTMLRenderer, JSONRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.permissions import IsAdminUser
from . import models
from . import serializers
class UserListView(generics.ListAPIView):
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
template_name = 'profile_list.html'
def get(self, request):
queryset = models.CustomUser.objects.all()
serializer_class = serializers.UserSerializer
return Response({'profiles': queryset})
My models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
class CustomUser(AbstractUser):
def __str__(self):
return self.email
I get an error "Object of type 'CustomUser' is not JSON serializable" when I request the API (http://127.0.0.1:8000/api/v1/users/)
Sorry, it's some different that initial question...
Yes, you can have both. The link you provided to docs has the following:
You can use TemplateHTMLRenderer either to return regular HTML pages using REST framework, or to return both HTML and API responses from a single endpoint.
When making an API request, set the ACCEPT request-header accordingly to html or json.
Finally I made some conditions in my view, and it's working
class UserListView(generics.ListAPIView):
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
permission_classes = (IsAdminUser,)
def get(self, request):
queryset = CustomUser.objects.all()
if request.accepted_renderer.format == 'html':
data = {'profiles': queryset}
return Response(data, template_name='profile_list.html')
else:
serializer = UserSerializer(queryset, many=True)
data = serializer.data
return Response(data)
How to return complex information in a single api under Django rest framework?
Assuming I have a model:
class Apple(models.Model):
color
size
shape
With a single api: /api/get-apples, I want to return a json like below:
{"red": {"round":[0.1,0.2,0.3],"spigold":[0.3,0.4,0.5]},
"yellow":{"round":[0.1,0.2,0.4],"spigold":[0.2,0.4,0.5]}}
What will be the best way to achieve this?
Create a serializers.py in your app's folder and add this code into it.
from rest_framework import serializers
class AppleSerializer(serializers.ModelSerializer):
class Meta:
model = Apple
fields = ('color', 'size', 'shape',)
In your views.py:
from rest_framework.generics import ListAPIView
from .serializers import AppleSerializer
class get_apples(ListAPIView):
serializer_class = AppleSerializer
def get_queryset(self):
# Here create your queryset.
queryset = Apple.objects.all()
return queryset
In your urls.py:
url(r'^api/get_apples/', views.get_apples.as_view(), name='get_apples'),
And you are good to go.
Output will be like this.
Let's say you have 2 apples.
{{"color": "red", "size": "blabla", "shape": "round"},{...(another apple json)}}
I'd edit my previous answer but I think it is a good example of using serializers with covering view,urls and serializer parts. Therefore, I didn't want to delete it :)
Here is how to return a complex json structure.
As I mentioned before, as far as I know, we can't do something like that by using rest framework's serializers class because it need comparison and grouping. I'll use rest_framework's api_view structure.
Also, I didn't understand types of size and shape in your model and what is your desired output. Therefore, this might be wrong but you'll get the idea anyway.
from rest_framework.decorators import api_view
from django.http import HttpResponse
import json
#api_view(['GET'])
def get_apples(request):
# define your queryset here as you want.
basket = Apple.objects.all()
data = {} # empty dictionary
for apple in basket:
if data.get(apple.color, None) is not None: # if same color of apple exists in basket
# check if shape exists or not.
if data.get(apple.color).get(apple.shape, None) is not None:
data.get(apple.color).get(apple.shape).append(apple.size)
else:
data.get(apple.color)[apple.shape] = [apple.size]
else:
data[apple.color] = {apple.shape: [apple.size]}
return HttpResponse(json.dumps(data), content_type='application/json; charset=utf-8')
I didn't test this code but probably this will work. Let me know if this works or not!
I want to check if a user is allowed to register for a specific event. I thought in order to save code I could do it like the login_required decorator right between the url() and MyClass.as_view(). But how do I get that slug? Or is this solution totally wrong? (I unfortunatelly can't use the user_passes_test because I don't want to test someting on the user but on the url.)
So I tryed this:
views.py
from registration.models import Event
from django.utils import timezone
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
def reg_is_open(event_slug):
"""
Return True if registration is open.
"""
event = get_object_or_404(Event, slug=event_slug)
if event.open_date <= timezone.now() and event.cut_off >= timezone.now():
return True
def allow_view(cls, **initkwargs):
"""
Check weather registration is open and user is logged in.
Returns to registration start page if registration is closed.
"""
slug = initkwargs.get('event') # Does not work!
if not reg_is_open(slug):
return HttpResponseRedirect(reverse('registration:event_index', args=slug))
return login_required(cls.as_view(**initkwargs))
# Also works when I remove **initkwargs. That means that what I'm looking for just passes...
urls.py
from django.conf.urls import patterns, url, include
from registration import views
event_patterns = patterns('',
url(r'^person/$', views.allow_view(views.PersonList), name='person_list'),
# instead of
# url(r'^person/$', login_required(views.PersonList.as_view()), name='person_list'),
# ...
urlpatterns = patterns('',
url(r'^(?P<event>[-a-zA-Z0-9_]+)/$', views.EventDetails.as_view(), name='event_index'),
url(r'^(?P<event>[-a-zA-Z0-9_]+)/', include(event_patterns)),
# ...
If I understand correctly, your view is a DetailView of the event, correct? In that case, use something along the lines of:
class EventDetailView(DetailView):
model = Event
def dispatch(self, request, *args, **kwargs):
if self.request.user not in self.object.registered_users.all():
return HttpResponseForbidden()
else:
return super(EventDetailView, self).dispatch(request, *args, **kwargs)
This assumes that you have a M2M key between User and Event models, called registered_users on the Event side; change the code to fit your situation.