nk20/apps/permission/models.py

136 lines
4.8 KiB
Python
Raw Normal View History

2020-02-09 16:35:15 +00:00
import functools
2019-09-18 12:26:42 +00:00
import json
2020-02-09 16:35:15 +00:00
import operator
2019-09-18 12:26:42 +00:00
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:
2020-02-09 16:35:15 +00:00
def __init__(self, model, query, type, field):
2019-09-18 12:26:42 +00:00
self.model = model
2020-02-09 16:35:15 +00:00
self.query = query
2019-09-18 12:26:42 +00:00
self.type = type
self.field = field
def applies(self, obj, permission_type, field_name=None):
2020-02-09 16:35:15 +00:00
"""
Returns True if the permission applies to
the field `field_name` object `obj`
"""
2019-09-18 12:26:42 +00:00
if ContentType.objects.get_for_model(obj) != self.model:
2020-02-09 16:35:15 +00:00
# The permission does not apply to the model
2019-09-18 12:26:42 +00:00
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
2020-02-09 16:35:15 +00:00
elif obj in self.model.objects.get(self.query):
return True
else:
return False
2019-09-18 12:26:42 +00:00
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')
2019-09-18 12:26:42 +00:00
]
model = models.ForeignKey(ContentType, on_delete=models.CASCADE, related_name='+')
2020-02-09 16:35:15 +00:00
# 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']}]
2019-09-18 12:26:42 +00:00
permission = models.TextField()
2020-02-09 16:35:15 +00:00
type = models.CharField(max_length=16, choices=PERMISSION_TYPES)
2019-09-18 12:26:42 +00:00
2020-02-09 16:35:15 +00:00
field = models.CharField(max_length=256, blank=True)
2019-09-18 12:26:42 +00:00
class Meta:
unique_together = ('model', 'permission', 'type', 'field')
def clean(self):
if self.field and self.type not in {'view', 'change'}:
2019-09-18 12:26:42 +00:00
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):
2020-02-09 16:35:15 +00:00
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
2019-09-18 12:26:42 +00:00
return None
2020-02-09 16:35:15 +00:00
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)
2019-09-18 12:26:42 +00:00
else:
2020-02-09 16:35:15 +00:00
# TODO: find a better way to crash here
raise Exception("Permission {} is wrong".format(self.permission))
2019-09-18 12:26:42 +00:00
def about(self, **kwargs):
permission = json.loads(self.permission)
2020-02-09 16:35:15 +00:00
query = self._about(permission, **kwargs)
return InstancedPermission(self.model, query, self.type, self.field)
2019-09-18 12:26:42 +00:00
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)