DRF #action decorator serializer.data response issue - python

I'm using #action in my ViewSet.
#action(detail=True, methods=('get', 'post', 'put', 'patch'))
def crontab(self, request, pk=None):
template_obj = self.get_object()
app_name = template_obj.application.name
template_name = template_obj.name
periodic_task_name = '%s:%s' % (app_name, template_name)
if request.method == 'GET':
periodic_task = PeriodicTask.objects.filter(
name=periodic_task_name)
if periodic_task.exists():
crontab_obj = periodic_task.get().crontab
serializer = serializers.CrontabScheduleSerializer(crontab_obj)
return Response(serializer.data)
return Response({'crontab': 'crontab does not exist'})
When condition if periodic_task.exists(): is True and return Response(serializer.data) executes I get this error related with TemplateSerializer. This serializer is used in ViewSet, not in my crontab action, and the 'application' field I do not touch in any way.
Any suggestions?

Solution. Put the logic from #action into a separate view with the same URL + '/your_action_name/'.
Also. Can do something like this in your #action, but this is not entirely correct:
if periodic_task.exists():
crontab_obj = periodic_task.get().crontab
serializer = serializers.CrontabScheduleSerializer(crontab_obj)
res_data = {**serializer.data}
return Response(res_data)

Related

Django Rest framework - data between two serializers do not validate

I have a API view in place where I first want to create a new user (already working) and second I want to return the new created user object using my UserSerializer (Not working).
views.py
#api_view(['POST'])
#permission_classes([AllowAny])
def user_create(request):
exception_handler = UserUnavailable
success_handler = UserCreated
if request.method == 'POST':
creation_serializer = CreateUserSerializer(data=request.data)
try:
if creation_serializer.is_valid(raise_exception=True):
creation_serializer.save()
user_serializer = UserSerializer(data=creation_serializer.instance.id)
if user_serializer.is_valid():
return JsonResponse({"status_code": success_handler.status_code,
"default_detail": success_handler.default_detail,
"default_code": success_handler.default_code,
"new_user": user_serializer,
}, safe=False)
except APIException:
return JsonResponse({"status_code": exception_handler.status_code,
"default_detail": exception_handler.default_detail,
"default_code": exception_handler.default_code
}, safe=False)
I can confirm that creation_serializer.instance.id contains the new users id.
serializers.py
class UserSerializer(serializers.ModelSerializer):
id = serializers.PrimaryKeyRelatedField(queryset=User.objects.all())
class Meta:
model = get_user_model()
fields = ('id', 'user')
read_only_fields = ('id', 'user')
I can also confirm that
if user_serializer.is_valid()
does not validate for some reason. Any ideas
Thanks in advance
To initialize a serializer with user instance instead of
user_serializer = UserSerializer(data=creation_serializer.instance.id)
You should write something like this:
user_serializer = UserSerializer(instance=creation_serializer.instance)
instead of
Also, you don't have to validate the object that has already been saved.
And create a response you should like this:
return JsonResponse({
"status_code": success_handler.status_code,
"default_detail": success_handler.default_detail,
"default_code": success_handler.default_code,
"new_user": user_serializer.data,
}, safe=False)
But using api_view is now a bad thing. It's much better to use ModelViewSet. You can use something like this:
class UserViewSet(ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
def get_serializer_class(self, *args, **kwargs):
if self.action == 'create':
return CreateUserSerializer
return super().get_serializer_class(*args, **kwargs)
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
response_serializer = self.serializer_class(instance=serializer.instance)
return Response(response_serializer.data, status=status.HTTP_201_CREATED, headers=headers)
And I'm sorry, I haven't tested it, there might be typos.
You can read about ModelViewsets more here https://www.django-rest-framework.org/api-guide/viewsets/#modelviewset

Own method in CreateView

I want to use my own method, which will return JsonResponse, but this method isn't called in this View by default. So maybe i need to rewrite another method or?
views.py:
class CreatePostJS(LoginRequiredMixin, SelectRelatedMixin, generic.CreateView):
fields = ('post_message', 'post_image')
model = models.Post
select_related = ('user',)
template_name = 'posts/post_list.html'
template_name = 'posts/post_form.html'
response_data = {}
response_data['text'] = model.post_message
response_data['posted'] = model.posted_at
response_data['user'] = model.user
def create_post(request):
if request.method == 'POST':
return JsonResponse(serializers.serialize('json', response_data), safe = False)
def form_valid(self, form):
self.object = form.save(commit = False)
self.object.user = self.request.user
self.object.save()
return super().form_valid(form)
https://docs.djangoproject.com/en/3.1/topics/class-based-views/intro/
Basically if you want to execute the method on POST requests, you can override the post method with your own. It will look more or less like this:
def post(self, request, *args, **kwargs):
my_json_response = self.create_post(request)
return super().post(request, *args, **kwargs)
That way there is no need to check for request.method in create_post, unless you plan to use it elsewhere.
When you work with generic views and want to override the default request handler, you need to overwrite the default method handler for the request method. In your case, it's the post() method from CreateView class.
I strongly recommend OOP way, so it would look like:
def get_request_data(self):
return {
'text': self.model.post_message,
'posted': self.model.posted_at,
'user': self.model.user
}
def create_post(request):
return JsonResponse(serializers.serialize('json', self.get_response_data()), safe = False)
def post(self, request, *args, **kwargs):
return self.create_post(request)
Note the if request.method == 'POST': is not needed, as post() method will only execute when POST request is being made in the current view.
You tried to initialize response_data outside a method, so as in my code you'd need to create another method for this - which I included in my example code.

Django REST framework - "Method \"GET\" not allowed." -

In creating Django REST framework, i'll get all the data using this code
views.py
#api_view(['GET', ])
def api_detail_educationlevel(request):
if request.method == 'GET':
educationlevel = EducationLevel.objects.all()
serializer = EducationLevelSerializer(educationlevel, many=True)
return Response(serializer.data)
urls.py
path('api/', views.api_detail_educationlevel),
but when i add in my views.py like this
#api_view(['PUT', ])
def api_update_educationlevel(request, pk):
try:
educationlevel = EducationLevel.objects.get(pk=pk)
except EducationLevel.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'PUT':
serializer = EducationLevelSerializer(educationlevel, data=request.data)
data = {}
if serializer.is_valid():
serializer.save()
data["success"] = "update successfull"
return Response(data=data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#api_view(['DELETE', ])
def api_delete_educationlevel(request, pk):
try:
educationlevel = EducationLevel.objects.get(pk=pk)
except EducationLevel.DoesNotExist:
return HttpResponse(status=404)
if request.method == 'DELETE':
operation = educationlevel.delete()
data ={}
if operation:
data["success"] = "delete successfull"
else:
data["failure"] = "delete failed"
return Response(data=data)
#api_view(['POST', ])
def api_create_blog_view(request):
authentication_classes = [SessionAuthentication, BasicAuthentication]
permission_classes = [IsAuthenticated]
def get(self, request, format=None):
content = {
'user': unicode(request.user), # `django.contrib.auth.User` instance.
'auth': unicode(request.auth), # None
}
return Response(content)
and in my urls.py
urlpatterns = [
path('api/', views.api_detail_educationlevel),
path('api/update/', views.api_update_educationlevel),
path('api/delete/', views.api_delete_educationlevel),
path('api/create/', views.api_create_blog_view),
]
urlpatterns = format_suffix_patterns(urlpatterns)
I dont know why i am getting this message,
in my path('api/update/', views.api_update_educationlevel),
in my path('api/delete/', views.api_delete_educationlevel),
in my path('api/create/', views.api_create_blog_view),
any idea why i am getting this message? i just want functional rest framework that can update/delete/insert data using user auth account
For each of these views (views.api_detail_educationlevel, views.api_update_educationlevel, views.api_delete_educationlevel, views.api_create_blog_view), you define #api_view(['*api method*', ]).
Only the views.api_detail_educationlevel has #api_view(['GET', ]) therefore allowing a GET method. The others don't. Either add a GET method to the other views or, like the documentation you follow, create a class containing each method.

Django Rest Framework - APIView Pagination

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)

django different serializer for GET and PUT - SyntaxError: invalid syntax

I'm trying to use get_serializer_class() and I'm getting error messages. Here's my view -
class CalendarDetail(RetrieveUpdateDestroyAPIView):
def get_serializer_class(self):
if self.request.method == 'GET':
serializer_class = CalendarGETSerializer
elif self.request.method == 'PUT':
serializer_class = CalendarPUTSerializer
return serializer_class
def get(self, request, format=None):
evntname = self.request.GET.get('name')
queryset = Evntmst.objects.filter(evntmst_name=evntname)
serializer = get_serializer_class(queryset)
if queryset:
return Response(serializer.data)
else:
raise Http404
def put(self, request, format=None):
serializer = get_serializer_class(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
And here's my urls.py -
url(r'^deploy/calendartest/$', CalendarDetail.as_view(), name='CalendarDetail'),
As soon as I try to hit the URL I get the following error message -
class CalendarDetail(RetrieveUpdateDestroyAPIView):
^
SyntaxError: invalid syntax
[14/Sep/2014 20:08:16] "GET /deploy/calendartest/?name=cal_daily HTTP/1.1" 500 5
9
I'm guessing it's something internal to the class that I'm calling properly and the syntax error is not necessarily true?
What I don't get is that I've designed the class as I would all my other classes that work, and I have a separate view doing a simple get with one serializer that returns that URL as a 200 NOT a 500.
This had a few issues. One was in the previous view I inadvertently removed a closing bracked on the previous line to the view I posted.
Second, get_serializer_class requires ()(queryset) to call the instance properly.
I'm trying to use get_serializer_class() and I'm getting error messages. Here's my view -
class CalendarDetail(RetrieveUpdateDestroyAPIView):
def get_serializer_class(self):
if self.request.method == 'GET':
serializer_class = CalendarGETSerializer
elif self.request.method == 'PUT':
serializer_class = CalendarPUTSerializer
return serializer_class
def get(self, request, format=None):
evntname = self.request.GET.get('name')
queryset = Evntmst.objects.filter(evntmst_name=evntname)
serializer = get_serializer_class()(queryset)
if queryset:
return Response(serializer.data)
else:
raise Http404

Categories