Django. Permissions to model instance? - django

I have next models:
class CategoryLesson(models.Model):
title = models.CharField()
class Lesson(models.Model):
title = models.CharField()
category = models.ForeignKey(CategoryLesson)
class UserRole(models.Model):
rolename = models.CharField()
lessons = models.ForeignKey(CategoryLesson)
group = models.ForeignKey(Group)
class SiteUser(models.Model):
username = models.OneToOneField(User)
roles = models.ManyToManyField(UserRole)
There are 3 categories, and no limit users.
And i cant understand how limit access some SiteUser to some CategoryLesson & Lesson . Already watched django-guardian, and dont see some of the differences between this application and django generic has_perm for this case. Is really i should create 3 models for each category??

Here you have to set a check in GET request whether your user has permission to access the specific categorylesson. Lets assume that your url is like this: 192.168.0.1:8000/categorylessson/?cat_id=1. (I am using a class based view here.)
class CategoryLessonList(TemplateView):
...
def get(self, request, *args, **kwargs):
siteuser= SiteUser.objects.get(username=request.User)
siteuser_roles= siteuser.roles.all()
specific_category= CategoryLesson.objects.get(id= int(request.GET.get('cat_id')))
for role in siteuser_roles:
r_lessons=role.objects.filter(lessons= specific_category)
if len(r_lessons)>0:
return super(CategoryLessonList, self).get(request, *args, **kwargs)
return redirect('/no-access')
PS: its an untested code.

Related

Django ORM: Join two tables using a linked table

I want to integrate a function has_permission(self, permission_name) in the class Profile.
For this I want to do make a ORM statement which returns a list of permission names.
The data model is:
1 User has 1 Profile (extends Django's User model)
Each Profile is assigned to one ProfileGroup.
However, different Profiles can belong to the same ProfileGroup.
Each ProfileGroup can have one or more ProfilePermissions. Groups can have the same subsets of ProfilePermissions. The Link-Table GroupPermissions handles this using two ForeignKeys instead of a ManyToMany relation.
My models.py looks like this:
class ProfileGroup(models.Model):
name = models.CharField(max_length=64)
class ProfilePermission(models.Model):
name = models.CharField(max_length=64)
class GroupPermissions(models.Model):
group = models.ForeignKey(ProfileGroup, on_delete=models.RESTRICT)
permission = models.ForeignKey(ProfilePermission, on_delete=models.RESTRICT)
class Meta:
constraints = [
models.UniqueConstraint(fields=['group', 'permission'], name='Unique')
]
def __str__(self):
return f'Group "{self.group.name}" has permission "{self.permission.name}"'
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
group = models.ForeignKey(ProfileGroup, on_delete=models.RESTRICT)
def __str__(self):
return f'Profile of {self.user.username}'
def has_permission(self, permission_name):
return True
The function has_permission (in the Profile class) should check if the permission permission is included in the list of Permissions. I tried it with
list_of_permissions = GroupPermissions.objects.filter(group=self.group)
But this returns a list of ProfilePermission. What I want is a list of names of the permission (permission.name).
So I have to join the two Tables GroupPermissions and ProfilePermission. I found things as prefetch_related and select_related but this does not work as I want.
Answering your question directly,
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
group = models.ForeignKey(ProfileGroup, on_delete=models.RESTRICT)
def __str__(self):
return f'Profile of {self.user.username}'
def has_permission(self, permission_name):
return GroupPermissions.objects.filter(group=self.group, permission__name=permission_name).exists()
But... My advice to you is to not try to reinvent the wheel. django.contib.auth has already groups and permissions, just use them ;-)

Django ModelForm field queryset based on another field

Consider my models.py,
PowerPolicy:
class PowerPolicy(models.Model):
name = models.CharField(max_length=15)
...
Group:
class Group(models.Model):
name = models.CharField(max_length=15)
#But then, we also have:
power_policies = models.ManytoManyField(PowerPolicy)
Player:
class Player(models.Model):
name = models.CharField(max_length=15)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
...
And then another model called,
UsePower:
class UserPower(models.Model):
player = models.ForeignKey(Player, on_delete=models.CASCADE)
power_policy = models.ForeignKey(PowerPolicy, on_delete=models.CASCADE)
...
But! Here's the catch: I want to make it so that my superuser (Note that my superuser isn't a player, he's simply a superuser) can only create a UsePower object of the Powers specified in the Player's Group. Now, I do know that I have to create a custom form and override the queryset of the power_policy field that returns, my the custom queryset according to my needs through a function.
- Here's what it would look something like:
class UsePowerForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UsePowerForm, self).__init__(*args, **kwargs)
def MyCustomFunctionThatReturnsTheQuerySet():
This function returns the Power policies that are allowed to the player in
their player Group. The only problem is,
Our little function here doesn't know how to get the player chosen.
could you help
return TheQuerySet
self.fields['power_policy'].queryset = MyCustomFunctionThatReturnsTheQuerySet()
And then use it on the Admin Site, by doing this:
class UsePowerAdmin(admin.ModelAdmin):
form = UsePowerForm
admin.site.register(UsePower, UsePowerForm)
I really hope this makes sense, and you guys could help me out.
Thank you for your time reading this, I honestly do appreciate it.
EDIT: Using form cleaning, or verifying during save, is not an option for me :(
You can get the player when the form is being initialized:
class UserPowerForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(UsePowerForm, self).__init__(*args, **kwargs)
player = Player.objects.get(id=self.initial['player'])
###from here you can use player to get the power policies and put into list
self.fields['power_policy'] = forms.ChoiceField(choices=power_policy_list)
class Meta:
model = UserPower
fields = ['player', 'power_policy']

Django Model Manager related filter

I'd like to filter the related entries in the manager:
class UserTravelsCarsManager(models.Manager):
def for_user(self, user):
return super(UserTravelsCarsManager, self).get_query_set().filter(user=user)
class TravelsCars(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=255)
...
objects = UserTravelsCarsManager()
class UserTravelsManager(models.Manager):
def for_user(self, user):
return super(UserTravelsManager, self).get_query_set().filter(user=user)
class Travels(models.Model, ClonableMixin):
user = models.ForeignKey(User)
vehicle = models.ForeignKey(TravelsCars)
...
objects = UserTravelsManager()
It won't work by itself. I get all of the cars for all users. I've tried:
return super(UserTravelsManager, self).get_query_set().filter(user=user, vehicle__user=user)
Which also doesn't work.
UPDATE:
Just to be clear the entries for Travels are filtered. Just the related TravelsCars aren't filtered if I query them through Travels.
What am I doing wrong?
Instead of super(UserTravelsCarsManager, self).get_query_set().filter... try to use self.filter(user=user). Same in UserTravelsManager

Django admin - giving users access to specific objects/fields?

I need to make an "owners" login for the admin. Say we have this model structure:
class Product(models.Model):
owner = models.ManyToManyField(User)
name = models.CharField(max_length=255)
description = models.CharField(max_length=255)
photos = models.ManyToManyField(Photo, through='ProductPhoto')
class Photo(models.Model):
order = models.IntegerField()
image = models.ImageField(upload_to='photos')
alt = models.CharField(max_length=255)
class ProductPhoto(models.Model):
photo = models.ForeignKey(Photo)
product = models.ForeignKey(Product)
We have a group called Owners that some users are part of. The ProductPhoto is a TabularInline on the Product admin page.
Now, owners need permission to edit
(primary goal) only products where product__in=user.products (so basically, only products owned by them).
(secondary goal) only the description and photos of products
How would I do this with Django's admin/permission system?
This is row (or object) level permission. Django provides basic support for object permissions but it is up to you to implement the code.
Luckily, there are a few apps that provide drop-in object-level permission framework. django-guardian is one that I have used before. This page on djangopackages.com provides some more that you can try out.
You may implement using get_form. For complex rule, you can add this too: https://github.com/dfunckt/django-rules
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
# permission check;
if form.base_fields and not request.user.is_superuser:
# when creating or updating by non-reviewer (except superuser)
# allow only reviewer to allow updating
form.base_fields['usertype'].disabled = True

django admin add data with fixed value in some field

class Facilites(models.Model):
id = models.CharField(max_length=32, primary_key=True)
name = models.CharField(max_length=128)
class Objects(models.Model):
name = models.CharField(max_length=64)
facilityid = models.ForeignKey(Facilities)
class Admins(models.Model):
user = models.OneToOneField(User)
facilities = models.ManyToManyField(Facilities)
def create_user_profile(sender, instance, created, **kwargs):
if created:
Admins.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
What i want is to have users (admins) only be able to add or modify "facilityid" in Objects to values specified in their Admins.facilities.
So if some user is named UserA and has facilities = ('FacA', 'FacB'), when he is adding a new object to DB, he shoudln't be able to add something like Object('Random object', 'FacC')
Also, he shouldn't be able to modify existing objects to facilities he doesn't belong to.
I have filtered the Objects with:
def queryset(self, request):
qs = super(ObjectsAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(facitityid__id__in = request.user.get_profile().facilities.all())
so users can only see the object that belong to their facilities. But i have no idea how to prevent them from adding/editing object out of their facilities.
edit:
found the answer here: https://stackoverflow.com/a/3048563/1421572
It turns out that ModelAdmin.formfield_for_foreignkey was the right answer in this situation: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
I would do this with either a pre-made facility list (i.e. You could create an integer field that is hooked to FACILITY_CHOICES for the user to select from.)
If only admins can do it then permissions sounds quite viable. You can also do form validation to check for errors against the db. Depending on how many facilities you have you may want a different approach.
You can do this same technique with a models.CharField as well. So perhaps assign a 3 letter facility code to each facility and require the entry to match one of the 3 letter strings. You could even have the list in a .txt file to read from. There are really so many ways to do this. I will provide an example of a pre-made facility list and accessing the facility a particular user belongs to from the api / template:
NYC_FACILITY = 0
LA_FACILITY = 1
ATL_FACILITY = 2
FACILITY_CHOICES = (
(NYC_FACILITY, 'NYC'),
(LA_FACILITY, 'LA'),
(ATL_FACILITY, 'ATL'),
class Facility(models.Model):
name = models.IntegerField(choices=FACILITY_CHOICES, default="NYC")
class Meta:
order_by = ['name']
verbose_name_plural = "facilities"
verbose_name = "facility"
def __unicode__(self):
return self.name
As far as viewing the facilities page that a particular user belongs to you will have a m2m one to one or FK relationship between the objects. If FK or m2m relationship then you will have access to additional methods of that model type. get_related However, I'm not going to use get_related in my example. Once you are in an instance you then have access to entry_set.
# models.py
from django.auth import User
class Person(User):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
facility_loc = models.ForeignKey('Facility') # ForeignKey used assuming only one person can belong to a facility.
slug = models.SlugField(unique=True)
def get_absolute_url(self):
return "/%s/%s/" % self.facility_loc % self.slug
# views.py - TemplateView is automatically given a context variable called params which parses data from the URL. So, I'll leave the regex in the URLConf up to you.
class UserFacilityView(TemplateView):
model = Facility
template_name = "user_facility.html"
Now in your template you should be able to access facility_set from a User instance or user_set from a facility instance.