Django permissions via related objects permissions - python

I am relatively new to Django and I'm looking for some guidance in how to setup permissions in a certain way. Basically I have an app that consists of a couple of models similar to this:
class Project(models.Model):
name = models.CharField(max_length=100)
users = models.ManyToManyField(CustomUser, related_name="projects")
class Task(models.Model):
name = models.CharField(max_length=100)
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="tasks")
class Asset(models.Model):
name = models.CharField(max_length=100)
project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name="assets")
My idea is that if a user is "assigned" to a project (via M2M field), that user will have access to all assets and tasks that are related to that Project. I have looked into django-guardian for per-object permissions and I think that could be the way to go, but to me it seems like I then would have to setup those permissions on each model..?
It feels like this should be a pretty common way of setting up permissions for any project-based app but I have a hard time finding similar examples and starting to wonder if I'm overthinking this or looking in the wrong direction?
Thank you,
Jonas

You can use django-rules to take advantage of object-level permissions without a database; with it, you can add permissions in many levels - models, views, templates, admin or DRF.
So, you'd need to create a predicate like
#rules.predicate
def is_project_manager(user, project):
return project.users == user
which will return True if the project's manager is the given user, False otherwise.
Then, to add it in a model, you'd do something like
import rules
from rules.contrib.models import RulesModel
class Project(RulesModel):
class Meta:
rules_permissions = {
"add": rules.is_project_manager,
"read": rules.is_authenticated,
}
There's ofc other considerations to attend to but I think that gives an overview of how it works.

Related

Custom django permissions for group chats?

I have a custom group model like this:
class MyGroup(models.Model):
name = models.CharField(max_length=200,null=True,blank=False,default="Group name")
members = models.ManyToManyField(get_user_model(), blank=True, related_name="grpmembers")
created_by = models.ForeignKey(get_user_model(), on_delete=models.DO_NOTHING, null=True, blank=False, related_name="createdby+")
created_at = models.DateTimeField(editable=False)
It works, it's fine, I override the save method in django admin so the created_by will point to the logged in user on save.
Problem #1
Even if you are the creator of the group, you can select yourself to either be in- or be removed from the group which kinda looks silly. I'm thinking of solving this by saying the user can view the group if they're in members or created_by.
Problem #2
Custom permission. I want to have some permissions, like:
Can view the group: which means the user is either the creator or is in the members list
Can edit the group: which means the user is the creator(can edit their own) or is staff(can edit anyone's stuff) or is superuser(root)
I can write it down and imagine how it would work, but I have no idea how to implement these.
I've found some ways, like creating a Meta and defining permissions there and also making the permissions as def(/functions), but how could I access the currently logged in user?
Like okay let's say I do
def can_view_group(self):
r1 = self.filter(members=req.user) # where req.uset is not accessible bc we're in models.py
, but how do I tell the permission to check the currently logged in user?
The problem is doing all the logic in models.
Over problem 1 you need to verify user is not owner before allow to leave in the views.py.
Over the problem 2 are 2 solutions, or you move can_view_group to views.py, or you define that in models as:
def can_view_group(self, user):
#all the logic
And you pass the user from views.py

How To Separate Objects In Django Admin?

I have an app called 'Product' with the following models.py:
class Product(models.Model):
product_id = models.CharField(max_length=50)
pub_date = models.DateTimeField(default=datetime.now)
title = models.CharField(max_length=255)
price = models.DecimalField(max_digits=8, decimal_places=2)
user = models.ForeignKey(User, on_delete=models.CASCADE)
featured = models.BooleanField(default=False)
I want to have two separate sections in Django Admin: Products and Featured Products, depending if featured = True or False.
So by default all products are listed under the Products section. But if featured = True they will be moved to Featured Products section. Can you please help me how to do that? Thanks in advance.
Three steps:
Write a proxy model for model Product.
Change the default manager to only returns featured products.
Register your proxy model in the admin like any other model.
You can read more about it here: Using Proxy Models to Customize the Django Admin
There are a couple of ways to do this. The simplest perhaps is to create a database view, and then encapsulate it using a django model. You can create a view like so in your database console:
CREATE VIEW view_name AS
SELECT columns
FROM tables
[WHERE conditions];
Once you have done that, you can reference the view in django like so:
class FeaturedProduct(modes.Model):
attr1 = models.CharField()
class Meta:
managed = False
db_table = '<name of your view here>'
Make sure that managed is set to False. Here is the relevant documentation for that. You want to do that because django is not creating this model for you, but rather you are creating it yourself.
Another way to do this would be to create a custom Manager. These managers allow you to modify the objects attribute of your model, allowing you to set a queryset that you want. I think you'd want to take a look at the Manager documentation and you can take a look at defining custom querysets for your objects.

What field Django used in FOO_set?

I am little bit comfused. Lets say I have such models.
models.py:
class Company(models.Model):
name = models.CharField(blank=False, null=False)
class Game(models.Model):
developer = models.ForeignKey(Company, on_delete=models.CASCADE)
publishers = models.ManyToManyField(Company)
If I use next code:
current_company = Company.object.get(pk=1)
current_company.game_set.all()
as I understand it return all games of current_company, but what field (developer or publishers) Django used?
But this code wouldn't be valid, for precisely this reason. If you tried to run it, Django would tell you that there was a conflict in the reverse relation.
If you have two relationships pointing to the same model, you need to explicitly set related_name on one of them to avoid this conflict.

User with permission to modify only one field of the model in the Django Admin

I have a user named ExpertUser who should only be able to modify an attribute of the called model (money) of the users in the Django Admin.
I have tried adding permissions in the model using the Meta but when entering with that permission I can not modify anything since I do not have access to any user.
My model is this:
class Client(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
money = models.FloatField(default=1000)
def __str__(self):
return self.user.username
class Meta:
permissions = (("can_view_money", "Can view money"),)
Have a look at Django Guardian. That provides more flexibility in assigning permissions on various levels. Django only lets you set permissions at object level, so in your case you were only able to set permission to create/edit/delete Client objects.
http://django-guardian.readthedocs.io/en/v1.4.8/index.html
If that doesn't suffice, there are a few other packages with the desired functionality:
https://djangopackages.org/grids/g/perms/

Extending Django User with subclasses

Due to my app requeriments I need an hierachy of users classes like:
class CommonUser(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
photo = models.ImageField(upload_to="profileImages",blank=True,null=True)
#HERE: Common properties for every user
class Meta:
abstract = True
class Installer(CommonUser):
#HERE: Specific properties for Installer Companies
class Administration(CommonUser):
#HERE: Specific properties for Administration
class Client(CommonUser):
#HERE: Specific properties for Clients
Well, in view.py I need to get the profile image for an user. I get the user by the request, so I have an models.User objects and I dont know to witch class it belong. Im not able to do:
request.user.commonuser.photo
because user object doesnt have any OnetoOne relation with commonuser but with installer/administration/client...
Any idea? Thanks!
I'm on mobile, so I can't test this, but...
I would set a "related_name" on your CommonUser.user field called "commonuser". Then you can just use:
request.user.commonuser.photo
like you are already.
I think the issue is that you are referencing a Django User object to reference a backwards relationship without the proper name.
First off, I think this model is more of a Profile than User. If you don't mind using 1.9 (and postgres) then this is a perfect usecase for a JSON field. You can filter with regular lookups and don't need to specify each type. That way you can also extend the user model in such a way that a user can fulfill many roles at once. The other thing I thought of was linking it the other way around:
class UserProfile(models.Model):
user = models.OneToOneField(User,on_delete=models.CASCADE)
# ...
class Installer(models.Model):
profile = models.ForeignKey(UserProfile, related_name='installer')
#HERE: Specific properties for Installer Companies
class Administration(models.Model):
profile = models.ForeignKey(UserProfile, related_name='admin')
#HERE: Specific properties for Administration
class Client(models.Model):
profile = models.ForeignKey(UserProfile, related_name='client')
#HERE: Specific properties for Clients

Categories