nk20/apps/permission/models.py

136 lines
4.8 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import functools
import json
import operator
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Q
from django.utils.translation import gettext_lazy as _
class InstancedPermission:
def __init__(self, model, query, type, field):
self.model = model
self.query = query
self.type = type
self.field = field
def applies(self, obj, permission_type, field_name=None):
"""
Returns True if the permission applies to
the field `field_name` object `obj`
"""
if ContentType.objects.get_for_model(obj) != self.model:
# The permission does not apply to the model
return False
if self.permission is None:
if permission_type == self.type:
if field_name is not None:
return field_name == self.field
else:
return True
else:
return False
elif obj in self.model.objects.get(self.query):
return True
else:
return False
def __repr__(self):
if self.field:
return _("Can {type} {model}.{field} in {permission}").format(type=self.type, model=self.model, field=self.field, permission=self.permission)
else:
return _("Can {type} {model} in {permission}").format(type=self.type, model=self.model, permission=self.permission)
class Permission(models.Model):
PERMISSION_TYPES = [
('add', 'add'),
('view', 'view'),
('change', 'change'),
('delete', 'delete')
]
model = models.ForeignKey(ContentType, on_delete=models.CASCADE, related_name='+')
# A json encoded Q object with the following grammar
# permission -> [] | {} (the empty permission representing all objects)
# permission -> ['AND', permission, …]
# -> ['OR', permission, …]
# -> ['NOT', permission]
# permission -> {key: value, …}
# key -> string
# value -> int | string | bool | null
# -> [parameter]
#
# Examples:
# Q(is_admin=True) := {'is_admin': ['TYPE', 'bool', 'True']}
# ~Q(is_admin=True) := ['NOT', {'is_admin': ['TYPE', 'bool', 'True']}]
permission = models.TextField()
type = models.CharField(max_length=16, choices=PERMISSION_TYPES)
field = models.CharField(max_length=256, blank=True)
class Meta:
unique_together = ('model', 'permission', 'type', 'field')
def clean(self):
if self.field and self.type not in {'view', 'change'}:
raise ValidationError(_("Specifying field applies only to view and change permission types."))
def save(self):
self.full_clean()
super().save()
def _about(_self, _permission, **kwargs):
self = _self
permission = _permission
if len(permission) == 0:
# The permission is either [] or {} and
# applies to all objects of the model
# to represent this we return None
return None
if isinstance(permission, list):
if permission[0] == 'AND':
return functools.reduce(operator.and_, [self._about(permission, **kwargs) for permission in permission[1:]])
elif permission[0] == 'OR':
return functools.reduce(operator.or_, [self._about(permission, **kwargs) for permission in permission[1:]])
elif permission[0] == 'NOT':
return ~self._about(permission[1], **kwargs)
elif isinstance(permission, dict):
q_kwargs = {}
for key in permission:
value = permission[key]
if isinstance(value, list):
# It is a parameter we query its primary key
q_kwargs[key] = kwargs[value[0]].pk
else:
q_kwargs[key] = value
return Q(**q_kwargs)
else:
# TODO: find a better way to crash here
raise Exception("Permission {} is wrong".format(self.permission))
def about(self, **kwargs):
permission = json.loads(self.permission)
query = self._about(permission, **kwargs)
return InstancedPermission(self.model, query, self.type, self.field)
def __str__(self):
if self.field:
return _("Can {type} {model}.{field} in {permission}").format(type=self.type, model=self.model, field=self.field, permission=self.permission)
else:
return _("Can {type} {model} in {permission}").format(type=self.type, model=self.model, permission=self.permission)
class UserPermission(models.Model):
user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
permission = models.ForeignKey(Permission, on_delete=models.CASCADE)