Adding a redirect to CreateAPIView - python

I want to redirect the user to the AddQuestionsView after the user creates a quiz(after adding title).
My CreateQuiz
class CreateQuizzView(CreateAPIView):
serializer_class = CreateQuizSerializer
My serializers.py file
class CreateQuizSerializer(serializers.ModelSerializer):
class Meta:
model = Quizzer
fields = ['title']
def create(self, validated_data):
user = self.context['request'].user
new_quiz = Quizzer.objects.create(
user=user,
**validated_data
)
return new_quiz
Can i add redirect by adding any Mixin or change need to change the GenericView.

An APIView is normally not used by a browser, or at least not directly, hence a redirect makes not much sense. The idea is that some program makes HTTP requests, and thus retrieves a response. Most API handlers will not by default follow a redirect anyway.
You can however make a redirect, by overriding the post method:
from django.shortcuts import redirect
class CreateQuizzView(CreateAPIView):
serializer_class = CreateQuizSerializer
def post(self, *args, **kwargs):
super().post(*args, **kwargs)
return redirect('name-of-the-view')

Related

Create model instance only if its user id field is equal to the logged-in user's id

In a django rest framework app, there's a TextViewSet. The Text object structure is as follows:
{
text: text_value,
author: author_id
}
When creating a new Text instance, I want to check if the supplied author_id equals the currently logged-in user's id.
I've read this question: When to use Serializer's create() and ModelViewset's perform_create(), but still can't decide whether to override Serializer's create(), ModelViewset's create() or perform_create() methods. What's the right method to override?
UPD:
models.py:
class Text(models.Model):
text = models.TextField()
author = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
serializers.py:
class TextSerializer(serializers.ModelSerializer):
class Meta:
model = Text
fields = ['author', 'text']
The question is in which of these methods should one perform this check if self.request.user.id != self.request.data['author']:?
You can override create() the method of your TextViewSet
views.py
from rest_framework.response import Response
class TextViewSet(ModelViewSet):
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
if request.user.id == request.data['author']:
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
else:
return Response("Unauthorized", status=status.HTTP_401_UNAUTHORIZED
The real question is why not just set author to the logged in user and not let the client send in any ID at all?
The normal way of doing this is by:
class MyViewSet(ModelViewSet):
...
def perform_create(self, serializer):
'''The logged in user is always the author'''
return serializer.save(author=self.request.user)
def get_queryset(self):
'''Limit the queryset to the author, i.e the logged in user, for fetching/updating data'''
return self.queryset.filter(author=self.request.user)
But if you really want to send in author-id then you can also use a custom Permission for it to have it re-usable.
from rest_framework.permissions import BasePermission
class UserIsAuthor(BasePermission):
default_author_field = "author"
def has_permission(self, request, view):
author_field = getattr(
view,
"permission_author_field",
self.default_author_field
)
return request.user.is_authenticated and (
request.user.pk == request.data.get(author_field)
)
used as:
class ExampleViewSet(ModelViewSet):
permission_classes = [UserIsAuthor]
# optional if the default "author" isnt what you want.
permission_author_field = "some_field_in_request_data"

User account delete in django's rest

I want create view which can deactivation user's account. when i create view and send delete request i have error - > "detail": "You do not have permission to perform this action.". i have authenticated permissionn but i am login in my account. i also use APIview
this is code ->
class DeleteAccount(generics.RetrieveUpdateDestroyAPIView):
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
def delete(self, request, *args, **kwargs):
user=self.request.user
user.delete()
return Response({"result":"user delete"})
Since you seem to be using a purely view-based approach, APIView might do the trick instead of the generic class. Additionally, setting the serializer class isn't necessary as well.
from rest_framework.views import APIView
class DeleteAccount(APIView):
permission_classes = [permissions.IsAuthenticated]
def delete(self, request, *args, **kwargs):
user=self.request.user
user.delete()
return Response({"result":"user delete"})
Additionally as a general practice, it's better/safer to reserve user deletion capabilities only for admin/staff users.

Why is django-rest-frameworks request.data sometimes immutable?

In my restful CreateAPIView I mutate my request.data dictionary.
Occasionally I receive an error not caught by my tests:
This QueryDict instance is immutable
For e.g. this:
class CreateView(CreateAPIView):
serializer_class = ...
queryset = ...
def post(self, request, *args, **kwargs):
request.data['user'] = request.user.pk
return self.create(request, *args, **kwargs)
request.data seems to be a normal dict in my tests. Why is it sometimes a QueryDict? How should this be dealt with? Should request.data not be mutated in general? How should you use the ModelSerializer class, when you need to populate some fields yourself?
Why this occasional behavior?
When we look into the SC of Request (as #Kenny Ackerman mentioned), it return a QueryDict object if you are passing a form media type ('application/x-www-form-urlencoded' or 'multipart/form-data') data to the view class. This check being execute within the is_form_media_type() method of Request class.
If you are passing a application/json data to the view, the request.data will be a dict object.
How to reproduce the behaviour?
It can be reproduce by using sending different ContentType data into view. (In POSTMAN tool, use form-data and raw JSON to get the behaviour)
How to get current logged-in user in serializer?
Method-1 pass extra argument to .save() (as #Linovia mentioned) by overriding the perform_create() method
class CreateView(CreateAPIView):
serializer_class = ...
queryset = ...
def post(self, request, *args, **kwargs):
request.data['user'] = request.user.pk
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(user=self.request.user)
Method-2 Use CurrentUserDefault() class as below
from django.contrib.auth import get_user_model
User = get_user_model()
class MySerializer(serializers.ModelSerializer):
user = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), default=serializers.CurrentUserDefault())
class Meta:
# your code
When you have to modify a QueryDict object received from a request, it is a immutable object, instead use this line of code if you wanna add attributes:
myNewRequest = request.GET.copy()
myNewRequest.data['some_attr'] = float(something)
based on the source code parser returns a querydict for data when the stream is empty(request.data calls _load_data_and_files method and _load_data_and_files calls _parse method).
and I think you can populate the fields using HiddenField or you can override the create or update method. for example
class TestSerializer(serializers.ModelSerializer):
user = serializers.HiddenField(default=serializers.CurrentUserDefault())
class Meta:
model = Test
fields = ('id', 'text', 'user')
def create(self, validated_data):
validated_data['populate_field'] = 'value'
return super().create(validated_data)
Note that this really depends on your selected parser specified in rest_framework's DEFAULT_PARSER_CLASSES and the content type of your request:
JSONParser is implemented as follows:
return json.load(decoded_stream, parse_constant=parse_constant)
FormParser as follows:
return QueryDict(stream.read(), encoding=encoding)

Rest Framework serializers, forbid users to change others password

I'm creating a simple web app and I cannot find any way to forbid other users from changing your password. This is minimal code:
# serializers.py
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
def create(self, validated_data):
# have to use custom create method because default method calls `User.objects.create()` which doesn't take care of password hashing and other important stuff
return User.objects.create_user(**validated_data)
class Meta:
model = User
fields = ('id', 'username', 'email', 'password')
read_only_fields = ('id', 'username')
# password is set to write_only because I don't want to send it to anybode (even though it's just a hash)
extra_kwargs = {'password': {'write_only': True}}
# views.py
from .serializers import UserSerializer
from rest_framework import generics
from django.contrib.auth.models import User
class UserDetails(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
I could take care of this myself by using APIView.
# views.py
from .serializers import UserSerializer
from rest_framework.views import APIView
from django.contrib.auth.models import User
class UserDetails(APIView):
def put(self, request, format=None):
serialized = UserSerializer(data=request.DATA)
if not serialized.is_valid():
return # probably HTTP 400 Error code
if request.user.id != serialized.data['id']:
# this if is what I'm trying to achieve
return # probably HTTP 403 Error code
user = User.objects.update(
id=serialized.data['id'],
email=serialized.data['email']
)
if 'password' in request.DATA:
user.set_password(request.DATA['password'])
return # probably HTTP 200 Error code
Unfortunately, that would have caused that scheme generated by rest_framework.schemas.get_schema_view would be incomplete. And I use for communication CoreAPI (which cannot communicate with something that is not described in the scheme) so I cannot do that. I haven't found anything in the official documentation.
This seems to be a too basic problem that has a super easy solution that I missed. Thanks for any ideas or places where to look.
PS: I'm using django2.1 with python3.6
Edit: osobacho's solutions is clean and works like charm. Anyway I also need to allow modifications (let's say of TODO-list) only to creator of that todo list. Thought that solution for password problem would be applicable but it's not.
You can get the user in the request. That way each user will only be able to change their own password.
class UserDetails(generics.RetrieveUpdateAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
def get_object(self):
return self.request.user
For your second question take a look at django_restframework http://www.django-rest-framework.org/api-guide/permissions/ permissions here is an example:
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Object-level permission to only allow owners of an object to edit it.
Assumes the model instance has an `owner` attribute.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Instance must have an attribute named `owner`.
return obj.owner == request.user
then you need to add to your view:
permission_classes = (IsOwnerOrReadOnly,)
hope it helps

Update and delete in same Api view without passing id in Url

How to perform crud operation in One URL End point in django rest framework?
Currently i am having 2 url end points
url(r'^recipient/$', views.RecipientView.as_view()), # in this APiview im performing get all and post
url(r'^recipient/(?P<pk>[0-9]+)/$', views.RecipientDetail.as_view()), # in this APiview im performing retrieve, update delete.
Now the requirement is i have remove 2nd url and perform all operations in first api view?
I am new to django framework can anyone please help me achieve this?
Below is my code.
View.py
class RecipientView(APIView):
def get(self, request, format=None):
Recipients = Recipient.objects.all()
serializer = RecipientSerializer(Recipients, many=True)
return Response(serializer.data)
def post(self, request, format=None):
serializer = RecipientSerializer(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)
"""
class RecipientDetail(APIView):
def get_object(self, pk):
try:
return Recipient.objects.get(pk=pk)
except Recipient.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
Recipient = self.get_object(pk)
serializer = RecipientSerializer(Recipient)
return Response(serializer.data)
def put(self, request, pk, format=None):
Recipient = self.get_object(pk)
serializer = RecipientSerializer(Recipient, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
Recipient = self.get_object(pk)
Recipient.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
"""
model.py
class Recipient(models.Model):
recipient = models.CharField(max_length=32, blank=False, null=False)
def __str__(self):
"""returns the model as string."""
return self.racipi
ent
serializer.py
class RecipientSerializer(serializers.ModelSerializer):
class Meta:
model = Recipient
fields = '__all__'
I am not able to update and delete in the same view please needed help?
You can avoid passing the ID in the URL with a POST request. Supply the ID and some kind of "action" verb, e.g. action=delete in the body of the request.
That's not considered RESTful though, partly because the HTTP DELETE and PUT verbs perfectly describes the requested operations, but also because POST is considered a non-idempotent method, meaning that the server state will change with each successful request. Being idempotent, duplicate DELETE/PUT (and GET for that matter) requests will leave the server in the same state.
It's not a major hassle to have a second route and view to implement the REST API so it's best to leave it as it is.
Your 2nd URL is receiving a parameters that can be used to fetch data object from the database and then perform any action on that particular instance. If you see the class RecipientDetail, you'll see all the methods are accepting a parameter called pk that relates to the object you want to fetch from database.
But your 1st URL is for generic actions like Create New Object or List All Objects and it is not good to use these endpoints to do instance specific actions.
You can read more about REST API standard enpoints to know details. Here is a reference link:
https://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api
The easiest way is to use DRF's ViewSet. It already provides the basic CRUD operation for you so you can just create the view in something like this:
# views.py
from rest_framework import viewsets
from .models import Recipient
from .serializers import RecipientSerializer
class RecipientViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing recipient instances.
"""
serializer_class = RecipientSerializer
queryset = Recipient.objects.all()
Since we are using ModelViewSet, it already provides actions like get, list, update, etc. as you can see in the documentation.
You can then use routers in your urls.py like this:
# urls.py
from rest_framework.routers import DefaultRouter
from myapp.views import RecipientViewSet
router = DefaultRouter()
router.register(r'recipients', RecipientViewSet)
urlpatterns = router.urls
The url above will generate a url that is like what you wrote in you question:
# Add recipient
POST /recipients/
# Get list of recipients
GET /recipients/
# Get recipient detail
GET /recipients/:recipient_id/
# Update recipient
PUT/PATCH /recipients/:recipient_id/
# Delete recipient
DELETE /recipients/:recipient_id/
Please take note that this is a simplified version and you can even create your own urls pattern with your specified actions.
UPDATE:
Thanks to mhawke for clarification. As what mhawke said in the comment, this may not be what the OP wanted, if you just want to avoid passing the ID in the url, then you can follow mawke's answer, and yes it is not considered RESTful.

Categories

Resources