Description:
The goal is to update all Spotlight fields on PUT/PATCH (update/partial update) if its status is YELLOW.
If status is RED || GREEN, it should update only its status and ignore any other fields. The workaround presented here is kind of smelly and it produces misleading responses when using PUT.
Is there any Django way to achieve this better than the presented workaround?
Workaround:
if instance.state == instance.STATE_YELLOW:
custom_data = request.data
else:
custom_data = {'state': request.data['state']}
Full code:
from stoplight.filters import StoplightFilter
from stoplight.models import Stoplight
from stoplight.permissions import (
IsSuperuserOrReadOnly
)
from stoplight.serializers import StoplightSerializer
from rest_framework import status
from rest_framework.mixins import CreateModelMixin, ListModelMixin, RetrieveModelMixin
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
class StoplightViewSet(GenericViewSet, CreateModelMixin, ListModelMixin, RetrieveModelMixin):
"""
API endpoint for Stoplights
"""
queryset = Stoplight.objects.all()
serializer_class = StoplightSerializer
filter_class = StoplightFilter
search_fields = ('name',)
permission_classes = (IsSuperuserOrReadOnly,)
def update(self, request, *args, **kwargs):
"""
Updates Stoplight state
"""
partial = kwargs.pop('partial', False)
instance = self.get_object()
if instance.state == instance.STATE_YELLOW:
custom_data = request.data
else:
custom_data = {'state': request.data['state']}
serializer = self.get_serializer(instance, data=custom_data, partial=partial)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
if getattr(instance, '_prefetched_objects_cache', None):
# If 'prefetch_related' has been applied to a queryset, we need to
# forcibly invalidate the prefetch cache on the instance.
instance._prefetched_objects_cache = {}
return Response(serializer.data, status=status.HTTP_200_OK)
def perform_update(self, serializer):
serializer.save()
def partial_update(self, request, *args, **kwargs):
kwargs['partial'] = True
return self.update(request, *args, **kwargs)
Related
class ExampleDestroyView(DestroyAPIView):
serializer_class = PetSerializer
queryset = Pet.objects.all()
lookup_field = "object_id"
def perform_destroy(self, instance):
self.data = {}
self.data['status'] = True
approval()
self.data['msg'] = "It removed"
return self.data
Here is my Sample Class ..... In this I need to Delete an objet.... It's deleting
But I am unable to pass the following Dict As an OutPut
How can I pass the status and a message in a dictionary
Override the destroy(...) method
from rest_framework.generics import DestroyAPIView
from rest_framework.response import Response
from rest_framework import status
class ExampleDestroyView(DestroyAPIView):
serializer_class = PetSerializer
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
data = self.perform_destroy(instance)
return Response(data=data, status=status.HTTP_204_NO_CONTENT)
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from snippets.models import Snippet
from snippets.serializers import SnippetSerializer
#api_view(['GET', 'POST'])
def snippet_list(request):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
snippets = Snippet.objects.all()
serializer = SnippetSerializer(snippets, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = SnippetSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I have a very simple APIView, but I don't know how to setup pagination here.
In this scenario I select an Event with given pk, then I get all the NewsItems assigned to this Event.
pagination_class = LimitOffsetPagination works OK when I define queryset at the beginning in ListCreateAPIView, for ex. queryset = Event.objects.all() but not with custom get:
class EventNewsItems(APIView):
pagination_class = LimitOffsetPagination
def get(self, request, pk, format=None):
#user = request.user
event = Event.objects.get(pk=pk)
news = event.get_news_items().all()
serializer = NewsItemSerializer(news, many=True, context={'request':request})
response = Response(serializer.data, status=status.HTTP_200_OK)
return response
Solved:
def get(self, request, pk, format=None):
#user = request.user
event = Event.objects.get(pk=pk)
news = event.get_news_items().all()
paginator = LimitOffsetPagination()
result_page = paginator.paginate_queryset(news, request)
serializer = NewsItemSerializer(result_page, many=True, context={'request':request})
response = Response(serializer.data, status=status.HTTP_200_OK)
return response
Another option would be inheriting from the pagination class, with fewer changes on the view class:
from rest_framework.pagination import LimitOffsetPagination
class EventNewsItems(APIView, LimitOffsetPagination):
def get(self, request, pk, format=None):
event = Event.objects.get(pk=pk)
news = event.get_news_items().all()
results = self.paginate_queryset(news, request, view=self)
serializer = NewsItemSerializer(results, many=True)
return self.get_paginated_response(serializer.data)
I have created a Q&A style example on this subject.
As a sort summary:
By utilizing the Django Rest Frameworks source code and how they handle pagination, we create the same methods inside our view class and we use them, in the same way your solution uses the default methods:
Taken from the above mentioned doc:
from rest_framework.settings import api_settings
from rest_framework.views import APIView
class MyView(APIView):
queryset = OurModel.objects.all()
serializer_class = OurModelSerializer
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS # cool trick right? :)
# We need to override get method to achieve pagination
def get(self, request):
...
page = self.paginate_queryset(self.queryset)
if page is not None:
serializer = self.serializer_class(page, many=True)
return self.get_paginated_response(serializer.data)
... Do other stuff needed (out of scope of pagination)
# 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)
Another way to paginate is by using the Paginator class.
In addition to the answer query, you must set the number of pages to be displayed and the range of elements that the page will have.
The page number and item range can be provided as part of the request parameters or by the way you select.
Taking as an example the case of the question:
from django.core.paginator import Paginator
class EventNewsItems(APIView):
def get(self, request, pk, format=None):
#user = request.user
event = Event.objects.get(pk=pk)
news = event.get_news_items().all()
# -----------------------------------------------------------
page_number = self.request.query_params.get('page_number ', 1)
page_size = self.request.query_params.get('page_size ', 10)
paginator = Paginator(news , page_size)
serializer = NewsItemSerializer(paginator.page(page_number) , many=True, context={'request':request})
# -----------------------------------------------------------
response = Response(serializer.data, status=status.HTTP_200_OK)
return response
this approach is almost similar as "giantas" answer, but modifyed, in this case you can modify each API as you need with page_size, and you don't need to modify settings.py globally
from rest_framework.pagination import PageNumberPagination
class EventNewsItems(APIView, PageNumberPagination):
#this will output only 3 objects per page
page_size = 3
def get(self, request, pk, format=None):
event = Event.objects.get(pk=pk)
news = event.get_news_items().all()
results = self.paginate_queryset(news, request, view=self)
serializer = NewsItemSerializer(results, many=True)
return self.get_paginated_response(serializer.data)
I'm struggling to figure out how to implement the Hyperlinked relationships for non-model querysets. I have a viewset:
class GGGViewSet(viewsets.ViewSet):
def list(self, request):
serializer_class = manufacture_serializer(ar)
serializer = serializer_class(
instance = sample.values(), many=True
)
return Response(serializer.data)
def retrieve(self, request, pk=None):
try:
anobject = sample[pk]
except KeyError:
return Response(status=status.HTTP_404_NOT_FOUND)
except ValueError:
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer_class = manufacture_serializer(ar)
serializer = serializer_class(instance=anobject)
return Response(serializer.data)
I am trying to get the value resource at /data/trait/ to be rendered as a link, where:
trait-list
data/trait/
{
"value": 12334,
"another_value": 45672,
}
trait-detail
data/trait/value/
{
"value":12334
}
Attempted:
url = serializers.HyperlinkedIdentityField(view_name='trait-list')
Error: AttributeError at /asvo/data/trait/ 'AObj' object has no attribute 'pk'.
Any ideas on the best way to approach this would be appreciated. :)
You were probably quite close. Based on the information provided, here's something that demonstrates HyperlinkedIdentityField usage without relying on an actual Django model. I had to use my imagination when filling in the details of your architecture.
from rest_framework import routers
from rest_framework import serializers
from rest_framework import status
from rest_framework import viewsets
from rest_framework.response import Response
# This goes in the URL routing file
router = routers.DefaultRouter()
router.register(r'trait', GGGViewSet, base_name='trait')
urlpatterns = router.urls
# The "model"
class Thing(object):
def __init__(self, pk, value, another_value):
self.pk = pk
self.value = value
self.another_value = another_value
# The "queryset"
sample = {
'1': Thing(1, 12334, 45672),
'2': Thing(2, 12335, 45673),
'3': Thing(3, 12336, 45674)
}
# The serializer
class manufacture_serializer(serializers.Serializer):
pk = serializers.HyperlinkedIdentityField(
view_name='trait-detail', read_only=True)
value = serializers.IntegerField()
another_value = serializers.IntegerField()
class Meta:
fields = ['pk', 'value', 'another_value']
# The view
class GGGViewSet(viewsets.ViewSet):
def list(self, request):
serializer = manufacture_serializer(
instance=sample.values(), many=True, context={'request': request})
return Response(serializer.data)
def retrieve(self, request, pk=None):
try:
anobject = sample[pk]
except KeyError:
return Response(status=status.HTTP_404_NOT_FOUND)
except ValueError:
return Response(status=status.HTTP_400_BAD_REQUEST)
serializer = manufacture_serializer(
instance=anobject, context={'request': request})
return Response(serializer.data)
I did not fully understand the second half of the question regarding the data/trait/ and data/trait/value/, but hopefully this is enough to further you along.
Cheers!
I am trying to return custom json with get_queryset but always get 404 error in response.
class TestViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows groups to be viewed or edited.
"""
queryset = Test.objects.all()
serializer_class = TestSerializer
def get_queryset(self):
if self.request.method == "GET":
content = {'user_count': '2'}
return HttpResponse(json.dumps(content), content_type='application/json')
If I delete everything starting from def I'll got correct response with standard json data. What I am doing wrong?
If you don't need a ModelViewSet and just want custom JSON on a GET request
You can also use an APIView, which doesn't require a model
class MyOwnView(APIView):
def get(self, request):
return Response({'some': 'data'})
and
urlpatterns = [
url(r'^my-own-view/$', MyOwnView.as_view()),
]
With a ModelViewSet
You've put the custom JSON into get_queryset, that's wrong. If you want to use a ModelViewSet, this by itself should be enough:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
This ModelViewSet comes with default implementations for .list(), .retrieve(), .create(), .update(), and .destroy(). Which are available for you to override (customize) as needed
Returning custom JSON from .retrieve() and/or .list() in ModelViewSet
E.g. to override .retrieve() to return custom view when retrieving a single object. We can have a look at the default implementation which looks like this:
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
So as an example to return custom JSON:
class TestViewSet(viewsets.ModelViewSet):
queryset = Test.objects.all()
serializer_class = TestSerializer
def retrieve(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
def list(self, request, *args, **kwargs):
return Response({'something': 'my custom JSON'})
There are 2 ways to custom the response in Class-based views with ModelViewSet
Solution 1: custom in views.py
class StoryViewSet(viewsets.ModelViewSet):
permission_classes = (permissions.AllowAny,)
queryset = Story.objects.all()
serializer_class = StorySerializer
def retrieve(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).retrieve(request)
return Response({'key': 'single value'})
def list(self, request, *args, **kwargs):
# ret = super(StoryViewSet, self).list(request)
return Response({'key': 'list value'})
Solution 2: custom in serializers.py (I recommend this solution)
class StorySerializer(serializers.ModelSerializer):
class Meta:
model = Story
fields = "__all__"
def to_representation(self, instance):
ret = super(StorySerializer, self).to_representation(instance)
# check the request is list view or detail view
is_list_view = isinstance(self.instance, list)
extra_ret = {'key': 'list value'} if is_list_view else {'key': 'single value'}
ret.update(extra_ret)
return ret
I need for a project a simple backend with the following functionality (all must be done via an API since this will be accessed by another program):
User can not do anything if he is not authenticated
In order to get authenticated the user knows "DEFAULT USER" credentials
2.1 The user tries to log in with the "DEFAULT USER" credentials
2.2 The application creates a new User with random pass and username (or token) and returns it to the user so that he can later use those new credentials to authenticate with the server
Authenticated users will only be able to CREATE 1 entry in the DB or update the entry created by them.
I have been trying to do this for the past few days however, all the django tutorials I managed to find for DJANGO-REST-FRAMEWORK did not help me.
Currently this is what I got:
urls.py
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from api import views
from api.serializers import UserSerializer
urlpatterns = [
url(r'^api-classes/$', views.UserStatisticsList.as_view()),
url(r'^api/$', views.userStatistics_list),
url(r'^users/$', views.UserList.as_view()),
]
urlpatterns = format_suffix_patterns(urlpatterns)
serializers.py:
from rest_framework import serializers
from api.models import UserStatistics
from django.contrib.auth.models import User
class UserStatisticsSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField(source='user.username')
class Meta:
model = UserStatistics
fields = ('id','user', 'last_modified', 'statistics_json',)
def create(self, validated_data):
"""
Create and return a new `UserStatistics` instance, given the validated data.
"""
return UserStatistics.objects.create(**validated_data)
def update(self, instance, validated_data):
"""
Update and return an existing `UserStatistics` instance, given the validated data.
"""
instance.last_modified = validated_data.get('last_modified', instance.last_modified)
instance.statistics_json = validated_data.get('statistics_json', instance.statistics_json)
instance.save()
return instance
class UserSerializer(serializers.ModelSerializer):
# userStat = serializers.PrimaryKeyRelatedField(many=False, queryset=UserStatistics.objects.all())
class Meta:
model = User
fields = ('id', 'username',)
# fields = ('id', 'username', 'userStat')
views.py:
from rest_framework import status
from rest_framework.decorators import api_view
from rest_framework.response import Response
from api.models import UserStatistics
from api.serializers import UserStatisticsSerializer, UserSerializer
from rest_framework import permissions
#api_view(['GET', 'POST'])
def userStatistics_list(request, format=None):
"""
List all code snippets, or create a new snippet.
"""
if request.method == 'GET':
userStat = UserStatistics.objects.all()
serializer = UserStatisticsSerializer(userStat, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = UserStatisticsSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
from rest_framework import mixins
from rest_framework import generics
class UserStatisticsList(
mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
"""
List all UserStatistics, or create a new UserStatistics.
"""
queryset = UserStatistics.objects.all()
serializer_class = UserStatisticsSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
# def get(self, request, format=None):
# userStat = UserStatistics.objects.all()
# serializer = UserStatisticsSerializer(userStat, many=True)
# return Response(serializer.data)
# def post(self, request, format=None):
# serializer = UserStatisticsSerializer(data=request.data)
# if serializer.is_valid():
# serializer.save()
# return Response(serializer.data, status=status.HTTP_201_CREATED)
# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
models.py:
from django.db import models
from jsonfield import JSONField
from django.contrib.auth.models import User
# Create your models here.
class UserStatistics(models.Model):
last_modified = models.DateTimeField(auto_now_add=True)
statistics_json = JSONField()
user = models.OneToOneField(User, related_name="creator")
class Meta:
ordering = ('last_modified','statistics_json',)
I also tried to implement a custom Authenticator which in my understanding is what gets called when an User wants to Authenticate ... :
import string
import random
from django.contrib.auth.models import User, check_password, ModelBackend
class SettingsBackend(ModelBackend):
"""
Authenticate against the settings ADMIN_LOGIN and ADMIN_PASSWORD.
Use the login name, and a hash of the password. For example:
ADMIN_LOGIN = 'admin'
ADMIN_PASSWORD = 'sha1$4e987$afbcf42e21bd417fb71db8c66b321e9fc33051de'
"""
ANDROID_LOGIN_USERNAME = "android"
ANDROID_LOGIN_PASSWORD = "android"
def authenticate(self, username=None, password=None):
login_valid = (ANDROID_LOGIN_USERNAME == username)
pwd_valid = check_password(password, ANDROID_LOGIN_PASSWORD)
if login_valid and pwd_valid:
# try:
# user = User.objects.get(username=username)
# except User.DoesNotExist:
# return None
# Create a new user. Note that we can set password
# to anything, because it won't be checked; the password
# from settings.py will.
user = User(username=random_string_generator(), password=random_string_generator())
user.save()
return user
return None
def random_string_generator(size=6, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))
def has_perm(self, user_obj, perm, obj=None):
if user_obj.username == settings.ANDROID_LOGIN_USERNAME:
return True
else:
return False
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
Can someone please point out what to do next ?
Thank you