I just finished coding a django app that helps music producers coordinate projects and I am trying to solve a small problem I am facing: For every music project, users are assigned specific roles (sound tech, drummer, production manager et cetera) and depending on their role, they should be able to see/do only some things within the project.
For example, the production manager should be able to see a project's budget and the drummer shouldn't; but they both should be able to see the location of the next recording session.
One thing to notice is that what some roles might be allowed to see might change from one project to the other. That is to say that it's possible that a Drummer should have visibility over the budget on one project and not on another (so I can't fix permissions based on the role and I need something more fluid where I can add and remove roles).
To make it simpler, we can consider that currently I only have the following models (on top of basic user model)
class JobProject(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
client = models.ManyToManyField(Company, related_name='client_company', blank=True)
title = models.CharField(max_length=40)
number = models.CharField(max_length=40, null=True)
class JobPosition(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.ForeignKey(Title, on_delete=models.CASCADE)
city = models.ForeignKey(City, on_delete=models.CASCADE)
rate = models.CharField(max_length=20)
jobproject = models.ForeignKey(JobProject, related_name='crew', on_delete=models.CASCADE, blank=True, null=True)
class Event(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
date = models.DateField(blank=True, null=True)
event_name = models.ForeignKey(EventName, blank=True, on_delete=models.CASCADE, related_name='events', null=True)
project = models.ForeignKey(JobProject, blank=True, on_delete=models.CASCADE, related_name='jobproject_events', null=True)
...
class dept_budget(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(choices=DEPARTMENTS, max_length=55)
budget = models.CharField(max_length=250)
jobproject = models.ForeignKey(JobProject, on_delete=models.CASCADE)
Anticipate you'll be changing permissions for those roles for each project? I'd create a through table for ProjectRole with a set of fields that includes the booleans for what you want to restrict by.
Fields like: project, role, view_budget, view_members, admin_project
You'd also have a table for UserRole which maps the users to project_roles. It becomes easy to query a project based permission for any given user. This all depends on if you are going to have fixed permissions for each project, or if they are going to vary. Like can a production manager see budget for every project, for every project they are a production manager, or just for this one project, but maybe another project doesn't let the production manager see the budget.
Decide where you want to assign the permissions (role, role & project) first. If it is just on the role, then you may want to use the built in Django permissions and a subsequent query to ensure they are also on the project.
Provide concrete details on your requirements, and then it will become easier to suggest a model structure to fit.
After update edit:
You need to decide if you think you are going to have a list of growing permissions, or if it is pretty much fixed. If fixed I would suggest adding a couple of booleans to the JobPosition class for the permissions. can_view_budget, can_add_members,etc. However, nothing is ever that simple as things grow. If it were me I'd add another table for JobPositionPermission like
class JobPositionPermission(models.Model):
class CategoryType(models.TextChoices):
ALLOW = 'allow'
DENY = 'deny'
description = models.CharField(max_length=64, help_text="Description of permission")
category_type = models.CharField(max_length=10, choices=CategoryType, default=CategoryType.ALLOW)
class JobPosition(models.Model):
...
permissions = models.ManyToManyField(JobPositionPermission)
Then you'd need to write something that checks if the person has a role that allows the permission. Alternatively, the deny option gives you the ability to prevent a certain role from doing something. I believe Django's permission system loads up all the permissions for a user once, and it refers to that values list. A similar approach might be warranted for your case for performance.
Something like in the auth model's source: (https://github.com/django/django/blob/05cde4764da022ae80e9d7d97ef67c30e896c607/django/contrib/auth/backends.py#L27)
def has_perm(self, user_obj, perm, obj=None):
return perm in self.get_all_permissions(user_obj, obj=obj)
and (https://github.com/django/django/blob/05cde4764da022ae80e9d7d97ef67c30e896c607/django/contrib/auth/backends.py#L67)
def _get_permissions(self, user_obj, obj, from_name):
"""
Return the permissions of `user_obj` from `from_name`. `from_name` can
be either "group" or "user" to return permissions from
`_get_group_permissions` or `_get_user_permissions` respectively.
"""
if not user_obj.is_active or user_obj.is_anonymous or obj is not None:
return set()
perm_cache_name = '_%s_perm_cache' % from_name
if not hasattr(user_obj, perm_cache_name):
if user_obj.is_superuser:
perms = Permission.objects.all()
else:
perms = getattr(self, '_get_%s_permissions' % from_name)(user_obj)
perms = perms.values_list('content_type__app_label', 'codename').order_by()
setattr(user_obj, perm_cache_name, {"%s.%s" % (ct, name) for ct, name in perms})
return getattr(user_obj, perm_cache_name)
First, you would have to assign different roles to the different users (you can do this manually, in the admin panel, or even computationally).
To create a group, you can try something like this, where drummer can be any role that you want:
from django.contrib.auth.models import Group doctor_group, created = Group.objects.get_or_create(name='drummer')
Next, you can add, set, remove, or clear a user's permissions.
For example, if I wanted to assign user b the role drummer, I would use something like this: doctor_group.permissions.set([permission_list])
Here is a full list of permission functions:
doctor_group.permissions.set([permission_list])
doctor_group.permissions.add(permission, permission, ...)
doctor_group.permissions.remove(permission, permission, ...)
doctor_group.permissions.clear()
You can also add users to groups using this syntax, depending on how your models are set up. user.groups.add(doctor_group)
Finally, you can check if a specific user is in a group by using the .exists() method, and authenticating a user.
Something like this should work:
def is_drummer(user):
return user.groups.filter(name='drummer').exists()
from django.contrib.auth.decorators import user_passes_test
#user_passes_test(is_drummer)
def my_view(request):
pass
You can learn more about specific Django groups & roles here.
Hi Djangonauts,
I am new to Django please forgive any silly mistake in logic or code.
Intro:
I am building a web app in which members can write posts on a topic and offer courses on that topic. Example A member can write a blog about doing a wheelie on a bicycle and offer courses on that.
What I want:
I want members who want to offer courses to be verified. Example: The member has to fill a form with their details like...
name, address, and photo ID. Plus pay a charge of $9.99 to get verified. After admin (I in this case) checks if everything is good I will approve them. and then they will be "Verified Members" and be able to offer courses
What I have so far: Right now members can offer courses as there is no verified clause
class Event(models.Model):
user = models.ForeignKey(User, related_name='seller')
post = models.ForeignKey(Post, related_name='course')
price = models.DecimalField(max_digits=6, decimal_places=2)
stock = models.IntegerField(validators=[MinValueValidator(1), MaxValueValidator(35)])
date = models.DateField()
time_from = models.TimeField()
time_to = models.TimeField()
event_types = (
('1', 'Webinar'),
('2', 'Actual Meet'),
)
event_choice = models.CharField(max_length=1, choices=event_types)
def get_absolute_url(self):
return reverse('posts:single', kwargs={'username': self.user.username,
'slug': self.post.slug})
def __str__(self):
return 'Meet for ' + self.post.title
How I plan to do it: I was planning to add a group in Django's admin AUTHENTICATION AND AUTHORIZATION
Home › Authentication and Authorization › Groups › Add group
Name: Verified
Permissions: Chosen permissions
event| event| Can add event
event| event| Can change event
event| event| Can delete event
Now what do I do from here?: Have I done things right so far, How do I take it from here. Do I create a model called verified and add forms.py to have members verified. How do permissions come in the picture.
My monkey patch (not a part of the question, for #Ojas Kale )
class Contact(models.Model):
user_from = models.ForeignKey(User, related_name='supporter')
user_to = models.ForeignKey(User, related_name='leader')
def __str__(self):
return '{} follows {}'.format(self.user_from, self.user_to)
User.add_to_class('following',
models.ManyToManyField('self', through=Contact, related_name='followers', symmetrical=False))
One way to go about it is adding a is_verified column in the user. There are various ways for doing this. but extending from abstractUser is probably the most straightforward and suitable in your case, since the class django.contrib.auth.models.AbstractUser provides the full implementation of the default User as an abstract model.
in your app_name.models.py create user class like this.
from django.db import models
from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
is_verified = models.BooleanField(default=False)
in your settingps.py
AUTH_USER_MODEL = 'app_name.User'
notice how app_name is used.
Now you can add as many attributes as you want as well.
By defaul is_verified is set to False, As soon as admin approves (verifies) the user change it to True.
Hope this helps.
I have a model Payment with fields: date_created, comment, description, amount. Each payment belongs to User.
class Payment(models.Model):
author = models.ForeignKey('auth.User')
amount = models.FloatField(max_length=10)
description = models.CharField(max_length=128)
created_date = models.DateTimeField(
default=datetime.now())
comment = models.TextField(max_length=256)
def __str__(self):
return self.description
Now I need to implement different roles for User: regular (can CRUD his payments), manager (can CRUD users), admin (can CRUD everything). So, the main question is how to implement this roles for users during registration (further it can't be changed). I also need to be able to set this role during registration via api (django rest framework).
Finally decided to user Django built-in Permissions
#e.g.
content_type = ContentType.objects.get_for_model(User)
permission = Permission.objects.get(codename='admin_can_manage_users')
user.user_permissions.add(permission)
I have no clue how to add permission with this model in Mezzanine:
class SitePermission(models.Model):
"""
Permission relationship between a user and a site that's
used instead of ``User.is_staff``, for admin and inline-editing
access.
"""
user = models.OneToOneField(user_model_name, verbose_name=_("Author"),
related_name="%(class)ss")
sites = models.ManyToManyField("sites.Site", blank=True,
verbose_name=_("Sites"))
class Meta:
verbose_name = _("Site permission")
verbose_name_plural = _("Site permissions")
How can I add a specific user in a view to a specific site? I have not found any documentation regarding this.
Here's how to do it:
from mezzanine.core.models import SitePermission
siteperms = SitePermission.objects.create(user=user)
siteperms.sites.add(site)
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