0

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)

giaggi
  • 542
  • 5
  • 16
  • This is also a really cool concept! You should consider uploading this to GitHub when your are done, I would love to help! – jahantaila Nov 09 '21 at 02:12
  • For clarity: do you have a front end built for this? Or are you doing everything in the Django Admin? – AMG Nov 12 '21 at 01:16
  • I have a front end, not built for this use case yet though. – giaggi Nov 13 '21 at 01:27

2 Answers2

1

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)
AMG
  • 1,606
  • 1
  • 14
  • 25
  • Thanks! Let me edit the question to be more specific then. Thanks a lot! – giaggi Nov 09 '21 at 12:49
  • Hey AMG, I edited the question with a little bit more details. I could add more specificity but I am also trying to avoid pasting a lot of code there so that it's not impossible for people to read. Thanks a lot for the help! – giaggi Nov 11 '21 at 02:11
  • Amazing! I am going to check it tomorrow morning first thing! – giaggi Nov 13 '21 at 01:28
  • Thanks a LOT @AMG! This might sound as a "weird" question but is there any change I could ping you for a potential freelance gig? – giaggi Nov 13 '21 at 03:21
  • :) not able at this time. But like boetis said, upload what you've got to a github repo and you might find that contributors like this idea and help you out, especially if there is already a code base to initialize with. It takes all the hypothetical out of the picture. Something like fiverr or similar would likely find you some experts in Django and modelling with much better opportunity to respond. – AMG Nov 13 '21 at 14:38
0

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.

jahantaila
  • 840
  • 5
  • 26
  • The issue I've seen with this approach is that it enforces that a drummer (in this example) has the same permissions for all projects which may be the desired affect. Each individual project may need a group created to ensure that a drummer in project A doesn't get to see project B by default. This approach is great, as long as permissions are the same across projects. – AMG Nov 09 '21 at 13:56
  • You can change the permissions if you want. Also consider creating groups of people who have access to each project, and then render out the HTML if they do. – jahantaila Nov 09 '21 at 15:34