Here the class WriteByAdminOnlyPermission is not working perfectly. This if request.method == 'GET': working but remaining condition is not working. My target is, only the admin can change information and the other people just can see. How can I do it? And where I did do wrong? please give me a relevant solution😥
Note: I used here custom User
class User(AbstractUser):
id = models.CharField(primary_key=True, max_length=10, default=uuid.uuid4, editable=False)
email = models.EmailField(max_length=50, unique=True, error_messages={"unique":"The email must be unique!"})
REQUIRES_FIELDS = ["email"]
objects = CustomeUserManager()
views.py:
class WriteByAdminOnlyPermission(BasePermission):
def has_permission(self, request, view):
user = request.user
if request.method == 'GET':
return True
if request.method in['POST' or 'PUT' or 'DELETE'] and user.is_superuser:
return True
return False
class ScenarioViewSet(ModelViewSet):
permission_classes=[WriteByAdminOnlyPermission]
serializer_class = ScenarioSerializer
queryset = Scenario.objects.all()
models.py:
class Scenario(models.Model):
id = models.CharField(primary_key=True, max_length=10, default=uuid.uuid4, editable=False)
Title = models.CharField(max_length=350, null=True, blank=False)
film_id = models.OneToOneField(Film, on_delete=models.CASCADE, related_name="ScenarioFilmID", null=True)
serializer.py:
class ScenarioSerializer(ModelSerializer):
class Meta:
model = Scenario
fields = "__all__"
urls.py:
router.register(r"scenario", views.ScenarioViewSet , basename="scenario")
Why you are using or operator in list, it can be simplified as ['POST', 'PUT', 'DELETE']so:
if request.method in ['POST', 'PUT', 'DELETE'] and user.is_superuser:
Edit
Try to use the IsAuthenticatedOrReadOnly class directly to allow unauthenticated users to perform GET requests and authenticated users to perform POST, PUT, and DELETE requests so:
from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission
class WriteByAdminOnlyPermission(BasePermission):
def has_permission(self, request, view):
user = request.user
if request.method in ['POST', 'PUT', 'DELETE'] and user.is_superuser:
return True
return IsAuthenticatedOrReadOnly().has_permission(request, view)
class ScenarioViewSet(ModelViewSet):
permission_classes = [WriteByAdminOnlyPermission]
serializer_class = ScenarioSerializer
queryset = Scenario.objects.all()
Related
I'm doing reviews on django, but I want the user to not be able to enter any name. I want the username in the reviews to match the username of his profile
models.py
class Reviews(models.Model):
name = models.CharField('Имя', max_length=100)
text = models.TextField('Отзыв', max_length=3400)
parent = models.ForeignKey('self', verbose_name='Родитель', on_delete=models.SET_NULL, null=True, blank=True)
book = models.ForeignKey(BookModel, verbose_name='книга', on_delete=models.CASCADE)
name_user = models.ForeignKey(User, on_delete=models.CASCADE, null=True)
views.py
class MoreInfoView(View):
""" """
def get(self, request, id):
book_info = BookModel.objects.filter(id=id).first()
stuff = get_object_or_404(BookModel, id=self.kwargs['id'])
total_likes = stuff.total_likes()
return render(request, 'bookapp/more_info.html', context={
'id': id,
'book_info': book_info,
'book': BookModel.objects.all(),
'total_likes': total_likes,
})
class AddReview(View):
"""Add Review"""
def post(self, request, pk):
form = ReviewForm(request.POST)
book = BookModel.objects.get(id=pk)
if form.is_valid():
form = form.save(commit=False)
form.book = book
form.save()
return HttpResponseRedirect(reverse('more_info', args=[pk]))
forms
class ReviewForm(forms.ModelForm):
class Meta:
model = Reviews
fields = ("name", "text", 'name_user')
You can add user manually after validating ReviewForm
I also added some changes(suggestions)
models.py
class Reviews(models.Model):
name = models.CharField('Имя', max_length=100)
text = models.TextField('Отзыв', max_length=3400)
parent = models.ForeignKey('self', verbose_name='Родитель', on_delete=models.SET_NULL, null=True, blank=True)
book = models.ForeignKey(BookModel, verbose_name='книга', on_delete=models.CASCADE, blank=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, blank=True)
Setting blank=True makes the field optional.
views.py
class MoreInfoView(View):
""" """
def get(self, request, id):
book_info = BookModel.objects.filter(id=id).first()
stuff = get_object_or_404(BookModel, id=self.kwargs['id'])
total_likes = stuff.total_likes()
return render(request, 'bookapp/more_info.html', context={
'id': id,
'book_info': book_info,
'book': BookModel.objects.all(),
'total_likes': total_likes,
})
class AddReview(View):
"""Add Review"""
def post(self, request, pk):
user = request.user
# User has to be authenticated to create a review. And backend must
# validate it. You should raise PermissionDenied as response or
# redirect user to the login page, or something similar.
if not request.user.is_authenticated:
raise PermissionDenied()
form = ReviewForm(request.POST)
book = BookModel.objects.get(id=pk)
if form.is_valid():
form = form.save(commit=False)
form.book = book
form.user = user
form.save()
return HttpResponseRedirect(reverse('more_info', args=[pk]))
forms.py
class ReviewForm(forms.ModelForm):
class Meta:
model = Reviews
fields = ("name", "text")
I would advise to work with a CreateView [Django-doc] that will simplify a lot of the logic. You can implement this as:
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import CreateView
class AddReviewView(LoginRequiredMixin, CreateView):
form_class = ReviewForm
def get_success_url(self):
return reverse('more_info', args=[self.kwargs['pk']])
def form_valid(self, form):
form.instance.book_id = self.kwargs['pk']
form.name_user = self.request.user
return super().form_valid(form)
In your ReviewForm you thus remove the name_user as fields element.
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: You can limit views to a class-based view to authenticated users with the
LoginRequiredMixin mixin [Django-doc].
Note: normally a Django model is given a singular name, so Review instead of Reviews.
Note: Models normally have no …Model suffix. Therefore it might be better to rename BookModel to Book.
I am creating an API for some polls. I need the author to be the only one who can view the votes, but the authenticated users to view the polls, questions, and to post votes. The author is just an instance of User, as like as the voters.
I'm using Djoser to provide the authentication API, model serializers, and breaking my mind between CBVs and viewsets.
Here are my models, if it can help.
from django.db import models
from django.contrib.auth import get_user_model
from django.utils import timezone
import datetime
User = get_user_model()
class Poll(models.Model):
title = models.CharField(max_length=200, verbose_name="Naslov ankete")
description = models.TextField(verbose_name="Opis ankete")
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="polls", verbose_name="Autor")
class Meta:
verbose_name = "Anketa"
verbose_name_plural = "Ankete"
def __str__(self):
return self.title
class Question(models.Model):
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="questions", verbose_name="Anketa")
question_text = models.CharField(max_length=200, verbose_name="Tekst pitanja")
pub_date = models.DateTimeField(verbose_name="Datum objavljivanja")
class Meta:
ordering = ["pub_date", "question_text"]
verbose_name = "Pitanje"
verbose_name_plural = "Pitanja"
def __str__(self):
return self.question_text
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
def verbose_question_text(self):
return f"Pitanje: {self.question_text}"
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE, related_name="choices", verbose_name="Pitanje")
choice_text = models.CharField(max_length=200, verbose_name="Tekst opcije")
# votes = models.IntegerField(default=0, verbose_name="Glasovi")
class Meta:
ordering = ["-votes", "pk"]
verbose_name = "Opcija"
verbose_name_plural = "Opcije"
def __str__(self):
return self.choice_text
class Vote(models.Model):
choice = models.ForeignKey(Choice, on_delete=models.CASCADE, related_name="votes", verbose_name="Opcija")
def __str__(self):
return self.choice.choice_text
P. S.: If you think the voting could be solved in a better way, please suggest it too.
I think you can do it by creating a custom permission (extended from IsAuthenticated class) and override method has_object_permission, see documentation.
from rest_framework.permissions import IsAuthenticated
class AuthorFullAccessUserPostOnly(IsAuthenticated):
def has_object_permission(self, request, view, obj):
if request.method == "POST":
return True
# This part better to implement with query
# Also, it will work only for Vote object.
# You need to extend this method if you are going to use for another objects
return request.user == obj.choice.question.poll.author
You can create a Permission class for each view (or viewset), or extend this one, respecting an object type
If you are wanting to use different conditions for different request methods you can use some kind of this logic
class PoolPermissions(IsAuthenticated):
def has_object_permission(self, request, view, obj):
if request.method == "POST":
self.has_post_object_permission(request, view, obj)
elif request.method == "GET":
self.has_get_object_permission(request, view, obj)
elif request.method == "PUT":
self.has_put_object_permission(request, view, obj)
# etc.
...
def has_get_object_permission(self, request, view, obj):
# your logic
pass
def has_post_object_permission(self, request, view, obj):
# your logic
pass
def has_put_object_permission(self, request, view, obj):
# your logic
pass
I'm getting KeyError: 'request' while i want to get the current user id through user request.
I tried something like this: validated_data['user_id'] = CarOwnerCarDetails.objects.get(user_id=self.context['request'].user.id) but it's throwing me KeyError.
How to get the current user id through request in serializers?
if any help would be much appreciated. Thank you so much in advance my friends.
models :
class CarOwnerCarDetails(models.Model):
user_id = models.OneToOneField(User, on_delete=models.CASCADE)
car_plate_number = models.CharField(max_length=20, null=True, blank=True)
class GetQuotes(models.Model):
user = models.ForeignKey(CarOwnerCarDetails, on_delete=models.CASCADE, blank=True, null=True)
subject = models.CharField(max_length=240, blank=False, null=True)
serializers :
class ShopGarageGetQuoteSerializer(ModelSerializer):
subject = CharField(error_messages={'required':'subject key is required', 'blank':'subject is required'})
user_id = serializers.CharField(read_only=True)
class Meta:
model = GetQuotes
fields= ['user_id', 'subject']
def create(self,validated_data):
subject = validated_data['subject']
validated_data['user_id'] = CarOwnerCarDetails.objects.get(user_id=self.context['request'].user.id)
quotes_obj = GetQuotes.objects.create(
subject=subject,
user_id=validated_data['user_id']
)
return validated_data
views.py :
class ShopGarageGetQuoteAPIView(APIView):
permission_classes = (IsAuthenticated,)
def post(self,request,*args,**kwargs):
data = request.data
serializer = ShopGarageGetQuoteSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response({'success' :'True','message' : 'Quotes send successfully','data' : serializer.data},status=200)
return Response(serializer.errors,status=400)
as is written in the Official Documentation the user information (if the Authentication framework is correctly setup) is available using request.user
In your View you have to pass it using the serializer's context
class ShopGarageGetQuoteAPIView(APIView):
permission_classes = (IsAuthenticated,)
def post(self,request,*args,**kwargs):
data = request.data
context = {'request': request}
serializer = ShopGarageGetQuoteSerializer(data=request.data, context=context)
if serializer.is_valid():
serializer.save()
return Response({'success' :'True','message' : 'Quotes send successfully','data' : serializer.data},status=200)
return Response(serializer.errors,status=400)
For Generic Views/Viewsets the standard context contains 'request', 'view' and 'format' but in a standard APIView you have to pass it manually
I'm working on a Rest API for a web blog, I have made the custom user class and related between custom user class and the post class, but when i try to make a post request and add a post to the database i'm stuck in serializering the post because of the the relation between the user and the post models, all i want to do is just make the author of the post is the current logged in user but i don't actually know how to do this
Custom user model:
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(max_length=255, unique=True, blank=False, null=False)
username = models.CharField(max_length=255, unique=True, blank=False, null=False)
date_joined = models.DateTimeField(auto_now_add=True)
is_active = models.BooleanField(default=True)
is_superuser = models.BooleanField(default=False)
is_staff = models.BooleanField(default=False)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['username']
objects = CustomUserManager()
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
return self.is_superuser
Post model:
class Post(models.Model):
title = models.CharField(max_length=255, blank=False, null=False)
content = models.CharField(max_length=10000, blank=False, null=False)
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
def __str__(self):
return self.title
Post serializer:
class PostSerializer(ModelSerializer):
class Meta:
model = Post
# I don't know if this way is right
fields = ('id', 'title', 'content', 'author')
extra_kwargs = {"author": {"read_only": True}}
def create(self, validated_data, request):
post = Post(
title=validated_data['title'],
content=validated_data['content'],
# I don't know what's gonna be assigned to the author
author=
)
post.save()
return post
Post View:
class AddPostView(APIView):
serializer_class = serializers.PostSerializer
def post(self, request):
serializer = serializers.PostSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
At first, it doesn't seem to me like you need to specify your custom create method in PostSerializer. And author field can't be read_only otherwise it will be ignored and won't be saved. So it could just look like this:
class PostSerializer(ModelSerializer):
class Meta:
model = Post
fields = ('id', 'title', 'content', 'author')
Then this is how you assign an author in your view action:
def post(self, request):
current_user = request.user
serializer = serializers.PostSerializer(data={**request.data, author=current_user})
...
I found a way that works fine for me.
Pass the context to the serializer from the view
Post View:
class AddPostView(APIView):
serializer_class = serializers.PostSerializer
def post(self, request):
serializer = serializers.PostSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
serializer.save()
return Response(data=serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Then here is the serializer. Post Serializer
class PostSerializer(ModelSerializer):
class Meta:
model = Post
fields = ('id', 'title', 'content', 'author')
extra_kwargs = {"author": {"read_only": True}}
def create(self, validated_data):
user = self.context['request'].user
post = Post(
title=validated_data['title'],
content=validated_data['content'],
author=user
)
post.save()
return post
I create a custom permission which authorizes GET, HEAD and OPTION for everyone and which authorizes NO other requests.
But my code doesn't work. I can make a POST request despite my permission ...
Anyone have a idea to solve my problem ?
My views.py:
class IsReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return False
class ViewPollViewSet(viewsets.ModelViewSet):
permission_classes = [
IsReadOnly,
]
serializer_class = ViewPollSerializer
queryset = ViewPoll.objects.all()
My serializers.py:
class ViewPollSerializer(serializers.ModelSerializer):
class Meta:
model = ViewPoll
fields = '__all__'
My models.py:
class ViewPoll(models.Model):
''' view poll '''
class Meta:
unique_together = ('poll', 'user')
poll = models.ForeignKey(Poll, on_delete=models.CASCADE, related_name="views", null=True)
user = models.ForeignKey(User,on_delete=models.CASCADE, related_name="views_poll", null=True)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return str(self.poll.question)[:30]
Call has_permission(...) method instead of has_object_permission(...) method
class IsReadOnly(permissions.BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
return False