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
Related
How do I access a current logged in user from a class-based view?
In a function-based view we can pass a request parameter but I can't pass a request parameter from a class view.
I have seen ways to do it in the internet but I can't understand it.
my models.py file
class Category(models.Model):
name = models.CharField(max_length=255)
def __str__(self):
return (self.name)
def get_absolute_url(self):
return reverse("home")
class Post(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(User, on_delete=models.CASCADE)
body = models.TextField(max_length=3500)
category = models.CharField(max_length=255, default="uncategorized")
views.py
class HomeView(ListView, LoginRequiredMixin):
model = Post
template_name = "home.html"
Thank you.
You can use self.request.user inside methods of class-based views; as an example:
class HomeView(ListView, LoginRequiredMixin):
model = Post
template_name = "home.html"
def get_context_data(self):
current_loggedin_user = self.request.user
# ...
Edit (just print the username):
class HomeView(ListView, LoginRequiredMixin):
model = Post
template_name = "home.html"
def __init__(self, *args, **kwargs):
print(self.request.user, self.request.user.username) # print user & username
return super().__init__(*args, **kwargs)
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
i'm trying to restrict users to view only the todos they have created using the dispatch method but i tried te below approach but still i'm seeing all todos appear under a user that did not create them
custom user model
from django.db import models
from django.contrib.auth.models import AbstractUser
# Create your models here.
class CustomUser(AbstractUser):
profession = models.CharField(null=True, blank=True, max_length=30)
todo model
from django.db import models
from django.contrib.auth import get_user_model
from django.urls import reverse
# Create your models here.
class Todo(models.Model):
title = models.CharField(max_length=120)
description = models.TextField(max_length=320)
to_be_done = models.DateTimeField(null=False)
date_posted = models.DateTimeField(auto_now_add=True)
user = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('todo_detail', args=[str(self.id)])
listview for todos
class TodoListView(LoginRequiredMixin, ListView):
model = Todo
template_name = 'todo_list.html'
login_url = 'login'
def dispatch(self, request, *args, **kwargs):
objects = self.objects.filter(user=self.request.user)
for obj in objects:
if obj.user != self.request.user:
raise PermissionDenied
return super().dispatch(request, *args, **kwargs)
any ideas on how i can adjust to implement above said task
Try something like this:
class TodoListView(LoginRequiredMixin, ListView):
model = Todo
def get_queryset(self):
qs = Todo.objects.filter(user=self.request.user)
return qs
I'm learning in DJango and I have learned alot of stuff from the documentation and also in StackOverflow. Right now, I'm kinda stuck and I just want to know who can I check in a class based view, if the user is in the manager column in job model/ It can also be in the manager model that's fine too.
I tried using UserPassesTestMixinin order to check if user is part of it but I'm getting an error of Generic detail view createjob must be called with either an object pk or a slug in the URLconf.
I just need someone to point me to the right direction or give me a hint.I also tried, this:
class createjob (LoginRequiredMixin,CreateView):
model = Job
fields = ['member','title', 'description', 'file']
def form_valid(self,form):
form.instance.manager=self.request.user
return super().form_valid(form)
But it's giving me an error of Cannot assign "<SimpleLazyObject: <User: edlabra>>": "Job.manager" must be a "Manager" instance.
Here's my views.py:
from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.views.generic import ListView, CreateView
from .models import Job, Member
from profiles.models import User
from django.contrib.auth.decorators import login_required
# Create your views here.
class jobs(LoginRequiredMixin,ListView):
model = Job
template_name = 'users/user_jobs.html'
context_object_name = 'jobs'
def get_queryset(self):
return Job.objects.filter(member__member=self.request.user)
class createdjobs(LoginRequiredMixin,ListView):
model = Job
template_name = 'users/manager_jobs.html'
context_object_name = 'jobs'
def get_queryset(self):
return Job.objects.filter(manager__manager=self.request.user)
class teamview(LoginRequiredMixin,ListView):
model = Member
template_name = 'users/manage_team.html'
context_object_name = 'members'
def get_queryset(self):
return Member.objects.filter(manager__manager=self.request.user)
class createjob (LoginRequiredMixin,UserPassesTestMixin,CreateView):
model = Job
fields = ['member','title', 'description', 'file']
def test_func(self):
job=self.get_object()
if self.request.user == Job.manager:
return True
return False
Models.py:
from django.db import models
from profiles.models import User
# Create your models here.
class Points (models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
points = models.IntegerField(default=0, null=False)
def __str__(self):
return self.user.username
class Profile (models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.png',upload_to='profile_pics')
def __str__(self):
return f'{self.user.username}Profile'
class Manager (models.Model):
name = models.CharField(max_length=30, blank=True, null=True)
manager = models.ForeignKey(User,on_delete=models.CASCADE)
def __str__(self):
return self.name
class Member (models.Model):
name = models.CharField(max_length=30, blank=True, null=True)
manager = models.ForeignKey(Manager, on_delete=models.CASCADE)
member = models.ForeignKey(User,on_delete=models.CASCADE)
def __str__(self):
return self.name
class Job (models.Model):
manager = models.ForeignKey(Manager, on_delete=models.CASCADE)
member = models.ForeignKey(Member, on_delete=models.CASCADE)
title = models.CharField(max_length=30, blank=False, null=False)
description = models.TextField()
datePosted = models.DateTimeField (auto_now = True)
file = models.FileField(null=True, blank=True,upload_to='job_files')
def __str__(self):
return self.title
assign user from manager table.
def form_valid(self,form):
form.instance.manager=Manager.objects.get(manager=self.request.user)
return super().form_valid(form)
I recently was able to figure out how to do this in the CreateView, but the same is not working for the UpdateView (Here's the original post on how to do it in the CreateView: (Django) Limited ForeignKey choices by Current User)
Essentially, I need it to display only the Universes created by the currently logged in user, but by default, it displays all universes.
When I try to set a form_class and have it mimic the solution for CreatView, it spits out an improperly configured error.
models.py:
class Universe(models.Model):
user = models.ForeignKey(User,related_name='universe',on_delete=models.CASCADE)
name = models.CharField(max_length=100, unique=True)
description = models.TextField(max_length=2000,blank=True,default="")
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('universe:singleuniverse',kwargs={'pk': self.pk})
class Meta:
ordering = ['name']
unique_together = ['user','name']
class Character(models.Model):
user = models.ForeignKey(User,related_name='characters',on_delete=models.CASCADE)
universe = models.ForeignKey("story_universe.Universe", on_delete=models.CASCADE)
name = models.CharField(max_length=255,unique=True)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('character_developer:singlecharacter',kwargs={'pk': self.pk})
class Meta():
ordering = ['name']
unique_together=['user','name']
views.py:
class UpdateCharacter(LoginRequiredMixin,generic.UpdateView):
model = Character
fields = ('universe','name')
template_name = 'character_developer/character_update_form.html'
UPDATE
The error was:
Error: ImproperlyConfigured at /characters/update/3/
UpdateCharacter is missing a QuerySet. Define UpdateCharacter.model, UpdateCharacter.queryset, or override UpdateCharacter.get_queryset().
and here's what the code looked like to get the error:
views.py:
class UpdateCharacter(LoginRequiredMixin,generic.UpdateView):
template_name = 'character_developer/character_update_form.html'
form_class = UpdateForm
def get_form_kwargs(self):
kwargs = super(UpdateCharacter,self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
forms.py
class UpdateForm(forms.ModelForm):
def __init__(self,*args,**kwargs):
user = kwargs.pop('user')
super(UpdateForm,self).__init__(*args,**kwargs)
self.fields['universe'].queryset = Universe.objects.filter(user=user)
class Meta:
model = Character
fields = ('universe','name')
I believe you need the following in your views.py (almost an exact extension of your CreateCharacter):
class UpdateCharacter(LoginRequiredMixin, generic.UpdateView):
model = Character
template_name ='character_developer/character_create.html'
form_class = UpdateForm
def get_form_kwargs(self):
kwargs = super(UpdateCharacter,self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
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)
I would caveat the above - make sure that the user instance in the request object is that of the currently logged in user, and has sufficient permission to update their own user - and can't somehow make a request on their behalf.