| Jul 28, 2014 | Django python
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