I am coding a restaurant reviews app. User can write a review and add pictures to it. I am setting up permissions to StarterPicsViewset so only the author of the review can Update it.
So I set up a custom permission but as I run it, I have a 'StarterPic' object has no attribute 'restaurant_review_review_author_id' error.
What I need is to get the review_author related to the restaurant_review_id so I can compare it to the request.user
Here is my models:
class Restaurant(models.Model):
adress = models.CharField(max_length=240)
name = models.CharField(max_length=140)
class RestaurantReview(models.Model):
review_author = models.ForeignKey(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE)
restaurant = models.ForeignKey(Restaurant, on_delete=models.CASCADE)
class StarterPic(models.Model):
restaurant_review = models.OneToOneField(RestaurantReview,
on_delete=models.CASCADE)
name_1 = models.CharField(max_length=40)
picture_1 = models.ImageField()
My view:
class StarterPicsViewset(viewsets.ModelViewSet):
queryset = models.InsidePic.objects.all()
serializer_class = serializers.InsidePicsSerializer
permission_classes = [IsAuthenticatedOrReadOnly, IsOwnReviewOrReadOnly]
and my permissions:
class IsOwnReviewOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.restaurant_review_review_author_id == request.user
StarterPic does not have such property like restaurant_review_review_author_id .
Try obj.restaurant_review.review_author to access review's author.
Related
I'm using django-guardian and I encountered some issues with the default mixins. And I want to know if there's a better way to do this.
GitHub Link: https://github.com/iaggocapitanio1/django_homepage
Problem:
If I want to limit access at both the model and object levels, using these two mixins (PermissionRequiredMixin, PermissionListMixin) is not a very easy task. Because the permissions_required attribute is overridden. To get around this I had to create a new attribute "object_permission" and do the following:
Model Looks like:
# Create your models here.
from django.db import models
from localflavor.br import models as localModels
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
pass
class Customer(models.Model):
user: User = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return f'{self.user.first_name} {self.user.last_name}'
class Company(models.Model):
user: User = models.OneToOneField(User, on_delete=models.CASCADE)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='comapnies')
def __str__(self):
return f'{self.user.first_name} {self.user.last_name}'
class Project(models.Model):
name = models.CharField(max_length=100)
owner = models.ForeignKey(Customer, on_delete=models.CASCADE, related_name='projects')
class Meta:
permissions = (('read_project', 'Read Project'),)
def __str__(self):
return self.name
class House(models.Model):
rooms = models.IntegerField()
postal_code = localModels.BRPostalCodeField()
project = models.ForeignKey(Project, on_delete=models.CASCADE)
Here I needed to create a new attribute ("object_permission") to limit object-level access
in the View:
class ProjectsListView(PermissionRequiredMixin, PermissionListMixin, ListView):
template_name = 'home/projects.html'
model = models.Project
permission_required = ["homepage.view_project"]
object_permission = ["read_project"]
redirect_field_name = 'next'
login_url = 'login/'
get_objects_for_user_extra_kwargs = {}
def get_object_permission(self, request: HttpRequest = None) -> List[str]:
if isinstance(self.object_permission, str):
perms = [self.object_permission]
elif isinstance(self.object_permission, Iterable):
perms = [p for p in self.object_permission]
else:
raise ImproperlyConfigured("'PermissionRequiredMixin' requires "
"'permission_required' attribute to be set to "
"'<app_label>.<permission codename>' but is set to '%s' instead"
% self.permission_required)
return perms
def get_get_objects_for_user_kwargs(self, queryset):
return dict(user=self.request.user,
perms=self.get_object_permission(self.request),
klass=queryset,
**self.get_objects_for_user_extra_kwargs)
#receiver(post_save, sender=models.Project)
def project_post_save(sender, **kwargs):
"""
Create a Profile instance for all newly created User instances. We only
run on user creation to avoid having to check for existence on each call
to User.save.
"""
project: models.Project = kwargs["instance"]
created: bool = kwargs["created"]
if created:
user = models.User.objects.get(pk=project.owner.user.id)
assign_perm("read_project", user, project)
Am I using the right approach to filter data relative to each user? How do I combine both the page access limitation and the relative data of each user in a class model view?
Let us imagine that I have two models.
First model contains curse details and user that created this course
class Course(models.Model):
course_name = models.CharField(max_length=100, null=False)
description = models.CharField(max_length=255)
user_profile = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
and my second model is:
class Lesson(models.Model):
course = models.OneToOneField(Course, on_delete=models.CASCADE) #
# inside the course I want my APIVIEW to list only the courses that current user created.
# OnetoOne relationship does not solve the problem.
status = models.CharField(choices=STATUS, null=False, default=GOZLEMEDE,max_length=20)
tariffs = models.FloatField(max_length=5,null=False,default=0.00)
continues_off = models.CharField(max_length=2)
user_profile = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
My serializers for both Models:
class LessonSerializer(serializers.ModelSerializer):
class Meta:
model = models.Lesson
fields = ('course', 'status', 'tariffs', 'continues_off', 'user_profile')
def create(self, validated_data):
lesson = models.Lesson.objects.create(
course = validated_data['course'],
status = validated_data['status'],
tariffs=validated_data['tariffs'],
continues_off=validated_data['continues_off'],
user_profile=validated_data['user_profile']
)
return lesson
class CourseSerializer(serializers.ModelSerializer):
"""Serializers Course content"""
class Meta:
model = models.Course
fields = '__all__'
def create(self,validated_data):
course = models.Course.objects.create(
course_name = validated_data['course_name'],
description=validated_data['description'],
user_profile=validated_data['user_profile']
)
return course
My Viewset:
class LessonViewset(viewsets.ModelViewSet):
model = models.Lesson
serializer_class = serializers.LessonSerializer
authentication_classes = (SessionAuthentication,)
permission_classes = (IsAuthenticated,BasePermission,)
def get_queryset(self):
user_current = self.request.user.id
return models.Lesson.objects.filter(user_profile=user_current)
How can I get the desired result. I want to get the courses for the current user and show them as a dropdown list in my API view. Just only the courses that user created should be in the dropdown list not all.
OnetoOne relationship gives all results of course table.
i think change your view code to :
def get_queryset(self,id):
return model.objects.filter(user_profile=id)
#You do not need to call it again when you put the Lesson on the model
\
Django API Issue
I am wanting to append a data field [site_name] stored in the parent table [USERaffiliates] to a child record [USERaffylinks].
site_name method: I am doing this so that I have access to the site_name field on a Vue.js component ... where I display the link data -- –
There is a 1:Many relationship with 1 USERaffiliates having many USERaffylinks.
I would so love some help - I have tried so many things but keep getting errors in the API when I try adding a new USERaffylinks record via the API browser screen.
I tried adding this method > def site_name - but failed.
Here's the traceback
traceback image
Without the site_name method, the API works just fine.
def site_name(self): is wrong somehow.
class USERaffylinks(models.Model):
owner_link_useraffyid = models.ForeignKey(USERaffiliates, on_delete=models.CASCADE)
owner_link_short = models.CharField(max_length=27, null=True, default=None, blank=True)
def site_name(self):
if USERaffiliates.objects.filter(id=self.owner_link_useraffyid).exists():
sitename = USERaffiliates.objects.get(id=self.owner_link_useraffyid)
return sitename.site_name
else:
return "ERROR"
FULL MODELS { without the def site_name(self) } -- API works fine with just this
class USERaffiliates(models.Model):
owneruserid = models.ForeignKey(User, on_delete=models.CASCADE, default=1)
user_category = models.BigIntegerField(null=True, default=None, blank=True )
registered_email = models.EmailField(null=True)
site_name = models.CharField(max_length=40, null=True)
... ... ....
class USERaffylinks(models.Model):
owner_link_useraffyid = models.ForeignKey(USERaffiliates, on_delete=models.CASCADE)
owner_link_short = models.CharField(max_length=27, null=True, default=None, blank=True)
... ... ...
USERaffylinks View
#action(methods=['POST'], detail=True)
class AffyPartnerLinksViewSet(viewsets.ModelViewSet):
queryset= USERaffylinks.objects.all() #need to filter on login user > owneruserid=request.user.id
serializer_class = USERaffylinksSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,) #later IsAuthentiated
Serialiser File
class USERaffiliatesSerializer(serializers.ModelSerializer):
class Meta:
model = USERaffiliates
fields = '__all__'
extra_fields = ['user_category_desc','user_status_desc','num_affy_links']
extra_kwargs = {'owneruserid': {'required':True}}
def get_field_names(self, declared_fields, info):
expanded_fields = super(USERaffiliatesSerializer, self).get_field_names(declared_fields, info)
if getattr(self.Meta, 'extra_fields', None):
return expanded_fields + self.Meta.extra_fields
else:
return expanded_fields
class USERaffylinksSerializer(serializers.ModelSerializer):
class Meta:
model = USERaffylinks
fields = '__all__'
extra_fields = ['site_name']
extra_kwargs = {'owner_link_useraffyid': {'required':True}}
def get_field_names(self, declared_fields, info):
expanded_fields = super(USERaffylinksSerializer, self).get_field_names(declared_fields, info)
if getattr(self.Meta, 'extra_fields', None):
return expanded_fields + self.Meta.extra_fields
else:
return expanded_fields
You are facing this error due to the wrong use of the actions decorator. As rest_framework's viewsets docs suggest that you can use actions decorator for making some extra actions for routing. In your case you are trying actions decorator on class which mean, you are making your whole class a routable which only accept POST method.
so instead of using this:
#action(methods=['POST'], detail=True)
class AffyPartnerLinksViewSet(viewsets.ModelViewSet):
queryset= USERaffylinks.objects.all() #need to filter on login user > owneruserid=request.user.id
serializer_class = USERaffylinksSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,) #later IsAuthentiated
try this:
class AffyPartnerLinksViewSet(viewsets.ModelViewSet):
queryset= USERaffylinks.objects.all() #need to filter on login user > owneruserid=request.user.id
serializer_class = USERaffylinksSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,) #later IsAuthentiated
Hope this will solve your issue.
USERaffylinks.owner_link_useraffyid is a ForeignKey so when accessed it will perform a DB query to access the related object. You need to fix the way you check to see if the ForeignKey contains a valid id, simply access the field and catch any DoesNotExist errors
class USERaffylinks(models.Model):
owner_link_useraffyid = models.ForeignKey(USERaffiliates, on_delete=models.CASCADE)
owner_link_short = models.CharField(max_length=27, null=True, default=None, blank=True)
def site_name(self):
try:
return self.owner_link_useraffyid.site_name
except USERaffiliates.DoesNotExist:
return "ERROR"
If the ForeignKey can be null (or at least invalid) then maybe it would make sense to make the field nullable instead of having a default value
I have just followed a small tutorial using DRF, but I can't figure how to like my model to his user when POSTing a new object
this is my model :
# Create your models here.
class Project(models.Model):
# project title
title = models.CharField(max_length=255, null=False)
# subtitle
subtitle = models.CharField(max_length=255, null=False)
#######
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
and so my serializer
class ProjectSerializer(serializers.ModelSerializer):
class Meta:
model = Project
fields = ("id", "title", "subtitle", "user_id")
so, now in the view I have access to the current_user with this :
request.user
class ListProjectsView(generics.ListCreateAPIView):
authentication_classes = [authentication.TokenAuthentication]
queryset = Project.objects.all()
serializer_class = ProjectSerializer
def list(self, request):
queryset = Project.objects.filter(user_id=request.user.id)
serializer = ProjectSerializer(queryset, many=True)
return Response(serializer.data)
[...]
def create(self, request, pk = None):
return super(ListProjectsView, self).create(request, pk = None)
I suppose there is a way to passe the request.user is the create in order to allow my Project.user_id to be filled ?
Whatever I'm doing, I can't manage to set the user, and i get the
django.db.utils.IntegrityError: null value in column "user_id" violates not-null constraint error
Any idea?
Thanks!
Try to override with following method. Everytime PUT/PATCH/CREATE operation is performed following method is called. This is the good way to pass the current user.
def perform_create(self, serializer):
serializer.save(user = self.request.user)
class Project(models.Model):
# project title
title = models.CharField(max_length=255, null=False)
# subtitle
subtitle = models.CharField(max_length=255, null=False)
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
okay so you have FK user but try to access it with the user_id = request.user.id
Just call
queryset = Project.objects.filter(user=request.user)
queryset = Project.objects.filter (user_id=request.user.id)
if you want to match the id you should put two __
like so user__id = request.user.id but I dont see any sence making it.
I'm having trouble defining object-level permissions for foreign-key relationships in my ModelViewSet. I'm not sure if it's entirely possible what I'm trying to do or if there's a better solution, but any hint in the right direction would be much appreciated. I've shortened the models and serializers for the sake of brevity.
I have the following models:
class Team(models.Model):
name = models.CharField(max_length=50)
class CustomUser(AbstractUser):
teams = models.ManyToManyField(Team)
class Client(models.Model):
name = models.CharField(max_length=50)
owner = models.ForeignKey(Team, on_delete=models.CASCADE)
class FinancialAccount(models.Model):
account_name = models.CharField(max_length=50)
client = models.ForeignKey(Client, on_delete=models.CASCADE)
Then I have the following serializers:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ('name', 'owner')
class FinancialAccountSerializer(serializers.ModelSerializer):
owner = serializers.SerializerMethodField()
class Meta:
model = FinancialAccount
fields = ('name', 'client', 'owner')
def get_owner(self, obj):
return client.owner.name
Then I'm trying to define a permission that I can use in all of my ModelViewSets. I'd like it to be somewhat dynamic as I have many more models than the ones above that are related to Client or even below FinancialAccount. The permission and viewset are as follows:
class IsOwnerTeam(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
teams = request.user.teams.values_list('name', flat=True)
return obj.owner in teams
class FinancialAccountViewSet(viewsets.ModelViewSet):
serializer_class = FinancialAccountSerializer
permission_classes = (IsOwnerTeam, )
def get_queryset(self):
teams = self.request.user.teams.all()
clients = Client.objects.filter(owner__in=teams)
return FinancialAccount.objects.filter(account__in=accounts)
So, right now I'm receiving this error: 'FinancialAccount' object has no attribute 'owner', which makes sense because I don't have an owner field on the FinancialAccount object. But, I thought if I had an owner field in the serializer (and put an owner field in each of the serializers) I could retrieve it that way. Any help would be appreciated!
You can do something like this:
class IsOwnerTeam(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if hasattr(obj, 'client'):
owner = obj.client.owner
else:
owner = obj.owner
teams = request.user.teams.values_list('name', flat=True)
return owner in teams