Struggled out of a Django-based project recently, I’d like to post such rather generic function Implementation of Inheritable Object-level Permissions, while I am always trying to improve it still. First of all, as I said, I didn’t research what others have done on this before I started my implementation, well, in fact, I feel hard to clearly describe the requirement and google it.
There is a django model Employee subclassed from django.contrib.auth.models.AbstractUser recording corp. employee info such as name, email, manager and so on. we want to implement:
Consider the performance issue of recursively querying out employee’s indirect manager, I subclassed django-mptt into model Employee, at the same time, used django-guardian for object-level permission management.
Assume the Employee model as following:
from django.contrib.auth.models import AbstractUser from mptt.models import MPTTModel, TreeForeignKey class Employee(MPTTModel, AbstractUser): """Employee model recording the basic info of employee """ manager = TreeForeignKey('self', null=True, blank=True, related_name='members')
Here I am directly giving the source code with some comments on the detail implementation. And it is rather easy to describe that in the Employee model:
import logging # I have implemented the [bulk_*](https://github.com/vitan/django-guardian) around django-guardian # by myself as the latest upstream haven't implement such bulk action yet. trying to contribute # my work [pull request](https://github.com/lukaszb/django-guardian/pull/245) to upstream. from guardian.shortcuts import bulk_remove_perm, bulk_assign_perm from django.contrib.auth.models import AbstractUser from mptt.models import MPTTModel, TreeForeignKey logger = logging.getLogger(__name__) class Employee(MPTTModel, AbstractUser): """Employee model recording the basic info of employee """ manager = TreeForeignKey('self', null=True, blank=True, related_name='members') class Meta: # Declare the permission--change_employee_info permissions=(('change_employee_info', 'Can change employee info'),) def save(self, *args, **kwargs): old_manager_pk = None if getattr(self, 'pk', None) is not None: try: obj = Employee.objects.get(pk=self.pk) if obj.manager: # Record the old manager pk in memory for future perm remove. old_manager_pk = obj.manager.pk except Employee.DoesNotExist: pass super(Employee, self).save(*args, **kwargs) # Record the new manager pk in memory for future perm assign. new_manager_pk = self.manager.pk if self.manager else None # Trigger function _manager_change once the manager changed if new_manager_pk != old_manager_pk: self._manager_change(old_manager_pk, new_manager_pk) def _manager_change(self, old_manager_pk, new_manager_pk): """Assign inherited change-employee-info permission to new manager, at the same time, remove such inherited permission from old manager. Note: plz keep the bulk_remove_perm() function called before bulk_assign_perm() """ # Using MPTT api, quickly query out the current employee's descendants for his/her # old manager perm removing. descendants = self.get_descendants(include_self=True) try: ancestors = Employee.objects.get(pk=old_manager_pk).get_ancestors(include_self=True) bulk_remove_perm('change_employee_info', ancestors, descendants) except Employee.DoesNotExist: # will be triggered when a new record inserted logger.debug("old managers doesn't exist yet!") try: # Also using MPTT api, quickly query out the new manager's ancestors for # his/her perm assigning. ancestors = Employee.objects.get(pk=new_manager_pk).get_ancestors(include_self=True) # Obviously, the inheritance of perm is causing DUPLICATE permission records inserted into # *Permission* model which will causing sql error. In my real project, I am using raw sql to # IGNORE the DUPLICATE permission records and forcely insert them, refering # [Django Bulk Insert Manager for ON DUPLICATE IGNORE and ON DUPLICATE KEY in MySQL](https://gist.github.com/datamafia/9671827) bulk_assign_perm('change_employee_info', ancestors, descendants) except Employee.DoesNotExist: # will be triggered when the new manager is None logger.debug("new managers doesn't exist yet")
In the above implementation, what I am proud of is to use manager change of employee as perm assign/remove trigger, instead employee’s member change or others. I feel django-mptt is powerful tool handling such issue.blog comments powered by Disqus