I have a CustomUser
model, each user could be linked to multiple companies. I have initiated my application with 3 generic Django groups; viewer, editor, and supervisor and each user could be a member in only one group.
class CustomUser(AbstractUser):
companies = models.ManyToManyField(Company, blank=True)
I have used some global Django permissions like the {app_label}.add_{class_name}
permisison with the editor group for an instance. In parallel, I used also django-guardian
to have some object-level permissions to get deeper with my objects' permissions as they vary depending on some of the object attributes' values.
So, at the end of the day, each user (depending on his group) has some global permissions and some object-level permissions. It works fine that way with no problem so far.
As stated earlier, each user is linked to multiple companies. Additionally, each object in my database is linked to a specific company. So, I need to have a higher level of permissions so that each user can deal and interact ONLY with the objects that are linked to one of his companies weather he/she is a viewer, editor, or supervisor.
Those are the solutions that I thought about, but each one of them has some drawbacks and I don't prefer actually:
django-guardian
by giving the permissions to the users of the groups instead of the groups directly, so that I can control giving the permissions to the company's objects only. But this solution has multiple drawbacks. It is a heavy operation to give permissions per object per user as I have many users in each company. Also, if I changed one of the user's group or if I added a new user, I have to do some extra work to add to or change his/her permissions.I like the second solution more than the first one as I can implement a unified Django mixin layer that can do that for me and just inherit it in all my target views.
Do you agree with any of those solutions or have another proposed one?
I followed another solution and I didn't do it through permissions. I have created a generic QuerySet
and added it with all the models that are linked to companies:
class ByCompanyQuerySet(models.QuerySet):
def all_by_companies(self, user):
if user.is_superuser:
return self.all()
return self.filter(company__in=user.companies.all())
class FooModel(models.Model):
foo = models.CharField(max_length=100)
objects = ByCompanyQuerySet.as_manager()
Then, I have used this all_by_company
in all my related Class-based views and also created a common function called get_object_by_company_or_404
to be used in all details views instead of get_object_or_404
:
def get_object_by_company_or_404(my_model, user, *args, **kwargs):
try:
return my_model.objects.select_related('company').all_by_companies(user).get(*args, **kwargs)
except my_model.DoesNotExist:
raise Http404('This item does not exist.')