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 likeThen 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)
and (https://github.com/django/django/blob/05cde4764da022ae80e9d7d97ef67c30e896c607/django/contrib/auth/backends.py#L67)