0004-a2_rbac-move-abstract-model-code-from-django_rbac-58.patch
src/authentic2/a2_rbac/models.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU Affero General Public License |
15 | 15 |
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
import hashlib |
|
17 | 18 |
import os |
18 | 19 |
from collections import namedtuple |
19 | 20 | |
21 |
from django.conf import settings |
|
22 |
from django.contrib.auth import get_user_model |
|
20 | 23 |
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation |
21 | 24 |
from django.contrib.contenttypes.models import ContentType |
22 | 25 |
from django.core.exceptions import ValidationError |
23 | 26 |
from django.core.validators import MinValueValidator |
24 | 27 |
from django.db import models |
28 |
from django.db.models.query import Prefetch, Q |
|
25 | 29 |
from django.urls import reverse |
26 | 30 |
from django.utils.text import slugify |
31 |
from django.utils.translation import gettext |
|
27 | 32 |
from django.utils.translation import gettext_lazy as _ |
28 | 33 |
from django.utils.translation import pgettext_lazy |
34 |
from model_utils.managers import QueryManager |
|
29 | 35 | |
30 | 36 |
from authentic2.decorators import errorcollector |
31 | 37 |
from authentic2.utils.cache import GlobalCache |
32 | 38 |
from authentic2.validators import HexaColourValidator |
39 |
from django_rbac import managers as rbac_managers |
|
33 | 40 |
from django_rbac import utils as rbac_utils |
34 |
from django_rbac.models import ( |
|
35 |
VIEW_OP, |
|
36 |
Operation, |
|
37 |
OrganizationalUnitAbstractBase, |
|
38 |
PermissionAbstractBase, |
|
39 |
RoleAbstractBase, |
|
40 |
RoleParentingAbstractBase, |
|
41 |
) |
|
41 |
from django_rbac.models import VIEW_OP, Operation |
|
42 | 42 | |
43 | 43 |
from . import app_settings, fields, managers |
44 | 44 | |
45 | 45 | |
46 |
class OrganizationalUnit(OrganizationalUnitAbstractBase): |
|
46 |
class AbstractBase(models.Model): |
|
47 |
"""Abstract base model for all models having a name and uuid and a |
|
48 |
slug |
|
49 |
""" |
|
50 | ||
51 |
uuid = models.CharField( |
|
52 |
max_length=32, verbose_name=_('uuid'), unique=True, default=rbac_utils.get_hex_uuid |
|
53 |
) |
|
54 |
name = models.CharField(max_length=256, verbose_name=_('name')) |
|
55 |
slug = models.SlugField(max_length=256, verbose_name=_('slug')) |
|
56 |
description = models.TextField(verbose_name=_('description'), blank=True) |
|
57 | ||
58 |
objects = rbac_managers.AbstractBaseManager() |
|
59 | ||
60 |
def __str__(self): |
|
61 |
return str(self.name) |
|
62 | ||
63 |
def __repr__(self): |
|
64 |
return f'<{self.__class__.__name__} {repr(self.slug)} {repr(self.name)}>' |
|
65 | ||
66 |
def save(self, *args, **kwargs): |
|
67 |
# truncate slug and add a hash if it's too long |
|
68 |
if not self.slug: |
|
69 |
self.slug = rbac_utils.generate_slug(self.name) |
|
70 |
if len(self.slug) > 256: |
|
71 |
self.slug = self.slug[:252] + hashlib.md5(self.slug).hexdigest()[:4] |
|
72 |
if not self.uuid: |
|
73 |
self.uuid = rbac_utils.get_hex_uuid() |
|
74 |
return super().save(*args, **kwargs) |
|
75 | ||
76 |
def natural_key(self): |
|
77 |
return [self.uuid] |
|
78 | ||
79 |
class Meta: |
|
80 |
abstract = True |
|
81 | ||
82 | ||
83 |
class OrganizationalUnit(AbstractBase): |
|
47 | 84 | |
48 | 85 |
RESET_LINK_POLICY = 0 |
49 | 86 |
MANUAL_PASSWORD_POLICY = 1 |
... | ... | |
131 | 168 |
('slug',), |
132 | 169 |
) |
133 | 170 | |
171 |
def as_scope(self): |
|
172 |
return self |
|
173 | ||
134 | 174 |
def clean(self): |
135 | 175 |
# if we set this ou as the default one, we must unset the other one if |
136 | 176 |
# there is |
... | ... | |
176 | 216 |
os.unlink(self.logo.path) |
177 | 217 | |
178 | 218 |
Permission.objects.filter(ou=self).delete() |
179 |
return super(OrganizationalUnitAbstractBase, self).delete(*args, **kwargs)
|
|
219 |
return super().delete(*args, **kwargs) |
|
180 | 220 | |
181 | 221 |
def natural_key(self): |
182 | 222 |
return [self.slug] |
... | ... | |
208 | 248 |
OrganizationalUnit._meta.natural_key = [['uuid'], ['slug'], ['name']] |
209 | 249 | |
210 | 250 | |
211 |
class Permission(PermissionAbstractBase): |
|
251 |
class Permission(models.Model): |
|
252 |
operation = models.ForeignKey(to=Operation, verbose_name=_('operation'), on_delete=models.CASCADE) |
|
253 |
ou = models.ForeignKey( |
|
254 |
to=rbac_utils.get_ou_model_name(), |
|
255 |
verbose_name=_('organizational unit'), |
|
256 |
related_name='scoped_permission', |
|
257 |
null=True, |
|
258 |
on_delete=models.CASCADE, |
|
259 |
) |
|
260 |
target_ct = models.ForeignKey(to='contenttypes.ContentType', related_name='+', on_delete=models.CASCADE) |
|
261 |
target_id = models.PositiveIntegerField() |
|
262 |
target = GenericForeignKey('target_ct', 'target_id') |
|
263 | ||
264 |
objects = rbac_managers.PermissionManager() |
|
265 | ||
212 | 266 |
class Meta: |
213 | 267 |
verbose_name = _('permission') |
214 | 268 |
verbose_name_plural = _('permissions') |
... | ... | |
226 | 280 |
object_id_field='admin_scope_id', |
227 | 281 |
) |
228 | 282 | |
283 |
def natural_key(self): |
|
284 |
return [ |
|
285 |
self.operation.slug, |
|
286 |
self.ou and self.ou.natural_key(), |
|
287 |
self.target and self.target_ct.natural_key(), |
|
288 |
self.target and self.target.natural_key(), |
|
289 |
] |
|
290 | ||
291 |
def export_json(self): |
|
292 |
return { |
|
293 |
"operation": self.operation.natural_key_json(), |
|
294 |
"ou": self.ou and self.ou.natural_key_json(), |
|
295 |
'target_ct': self.target_ct.natural_key_json(), |
|
296 |
"target": self.target.natural_key_json(), |
|
297 |
} |
|
298 | ||
299 |
def __str__(self): |
|
300 |
ct = ContentType.objects.get_for_id(self.target_ct_id) |
|
301 |
ct_ct = ContentType.objects.get_for_model(ContentType) |
|
302 |
if ct == ct_ct: |
|
303 |
target = ContentType.objects.get_for_id(self.target_id) |
|
304 |
s = f'{self.operation} / {target}' |
|
305 |
else: |
|
306 |
s = f'{self.operation} / {ct.name} / {self.target}' |
|
307 |
if self.ou: |
|
308 |
s += gettext(' (scope "{0}")').format(self.ou) |
|
309 |
return s |
|
310 | ||
229 | 311 | |
230 | 312 |
Permission._meta.natural_key = [ |
231 | 313 |
['operation', 'ou', 'target'], |
... | ... | |
233 | 315 |
] |
234 | 316 | |
235 | 317 | |
236 |
class Role(RoleAbstractBase): |
|
318 |
class Role(AbstractBase): |
|
319 |
ou = models.ForeignKey( |
|
320 |
to=rbac_utils.get_ou_model_name(), |
|
321 |
verbose_name=_('organizational unit'), |
|
322 |
swappable=True, |
|
323 |
blank=True, |
|
324 |
null=True, |
|
325 |
on_delete=models.CASCADE, |
|
326 |
) |
|
327 |
members = models.ManyToManyField( |
|
328 |
to=settings.AUTH_USER_MODEL, swappable=True, blank=True, related_name='roles' |
|
329 |
) |
|
330 |
permissions = models.ManyToManyField( |
|
331 |
to=rbac_utils.get_permission_model_name(), related_name='roles', blank=True |
|
332 |
) |
|
237 | 333 |
name = models.TextField(verbose_name=_('name')) |
238 | 334 |
admin_scope_ct = models.ForeignKey( |
239 | 335 |
to='contenttypes.ContentType', |
... | ... | |
262 | 358 |
default=True, verbose_name=_('Allow adding or deleting role members') |
263 | 359 |
) |
264 | 360 | |
361 |
objects = rbac_managers.RoleQuerySet.as_manager() |
|
362 | ||
363 |
def add_child(self, child): |
|
364 |
RoleParenting = rbac_utils.get_role_parenting_model() |
|
365 |
RoleParenting.objects.soft_create(self, child) |
|
366 | ||
367 |
def remove_child(self, child): |
|
368 |
RoleParenting = rbac_utils.get_role_parenting_model() |
|
369 |
RoleParenting.objects.soft_delete(self, child) |
|
370 | ||
371 |
def add_parent(self, parent): |
|
372 |
RoleParenting = rbac_utils.get_role_parenting_model() |
|
373 |
RoleParenting.objects.soft_create(parent, self) |
|
374 | ||
375 |
def remove_parent(self, parent): |
|
376 |
RoleParenting = rbac_utils.get_role_parenting_model() |
|
377 |
RoleParenting.objects.soft_delete(parent, self) |
|
378 | ||
379 |
def parents(self, include_self=True, annotate=False, direct=None): |
|
380 |
return self.__class__.objects.filter(pk=self.pk).parents( |
|
381 |
include_self=include_self, annotate=annotate, direct=direct |
|
382 |
) |
|
383 | ||
384 |
def children(self, include_self=True, annotate=False, direct=None): |
|
385 |
return self.__class__.objects.filter(pk=self.pk).children( |
|
386 |
include_self=include_self, |
|
387 |
annotate=annotate, |
|
388 |
direct=direct, |
|
389 |
) |
|
390 | ||
391 |
def all_members(self): |
|
392 |
User = get_user_model() |
|
393 |
prefetch = Prefetch('roles', queryset=self.__class__.objects.filter(pk=self.pk), to_attr='direct') |
|
394 | ||
395 |
return ( |
|
396 |
User.objects.filter( |
|
397 |
Q(roles=self) |
|
398 |
| Q(roles__parent_relation__parent=self) & Q(roles__parent_relation__deleted__isnull=True) |
|
399 |
) |
|
400 |
.distinct() |
|
401 |
.prefetch_related(prefetch) |
|
402 |
) |
|
403 | ||
404 |
def is_direct(self): |
|
405 |
if hasattr(self, 'direct'): |
|
406 |
if self.direct is None: |
|
407 |
return True |
|
408 |
return bool(self.direct) |
|
409 |
return None |
|
410 | ||
265 | 411 |
def get_admin_role(self, create=True): |
266 | 412 |
from . import utils |
267 | 413 | |
... | ... | |
503 | 649 |
] |
504 | 650 | |
505 | 651 | |
506 |
class RoleParenting(RoleParentingAbstractBase): |
|
507 |
class Meta(RoleParentingAbstractBase.Meta): |
|
652 |
class RoleParenting(models.Model): |
|
653 |
parent = models.ForeignKey( |
|
654 |
to=rbac_utils.get_role_model_name(), |
|
655 |
swappable=True, |
|
656 |
related_name='child_relation', |
|
657 |
on_delete=models.CASCADE, |
|
658 |
) |
|
659 |
child = models.ForeignKey( |
|
660 |
to=rbac_utils.get_role_model_name(), |
|
661 |
swappable=True, |
|
662 |
related_name='parent_relation', |
|
663 |
on_delete=models.CASCADE, |
|
664 |
) |
|
665 |
direct = models.BooleanField(default=True, blank=True) |
|
666 |
created = models.DateTimeField(verbose_name=_('Creation date'), auto_now_add=True) |
|
667 |
deleted = models.DateTimeField(verbose_name=_('Deletion date'), null=True) |
|
668 | ||
669 |
objects = rbac_managers.RoleParentingManager() |
|
670 |
alive = QueryManager(deleted__isnull=True) |
|
671 | ||
672 |
def natural_key(self): |
|
673 |
return [self.parent.natural_key(), self.child.natural_key(), self.direct] |
|
674 | ||
675 |
class Meta: |
|
508 | 676 |
verbose_name = _('role parenting relation') |
509 | 677 |
verbose_name_plural = _('role parenting relations') |
678 |
unique_together = (('parent', 'child', 'direct'),) |
|
679 |
# covering indexes |
|
680 |
index_together = (('child', 'parent', 'direct'),) |
|
510 | 681 | |
511 | 682 |
def __str__(self): |
512 | 683 |
return '{} {}> {}'.format(self.parent.name, '-' if self.direct else '~', self.child.name) |
src/django_rbac/migrations/0009_auto_20221004_1343.py | ||
---|---|---|
1 |
# Generated by Django 2.2.26 on 2022-10-04 11:43 |
|
2 | ||
3 |
from django.db import migrations |
|
4 | ||
5 | ||
6 |
class Migration(migrations.Migration): |
|
7 | ||
8 |
dependencies = [ |
|
9 |
('django_rbac', '0008_add_roleparenting_soft_delete'), |
|
10 |
] |
|
11 | ||
12 |
operations = [ |
|
13 |
migrations.DeleteModel( |
|
14 |
name='OrganizationalUnit', |
|
15 |
), |
|
16 |
migrations.RemoveField( |
|
17 |
model_name='role', |
|
18 |
name='members', |
|
19 |
), |
|
20 |
migrations.RemoveField( |
|
21 |
model_name='role', |
|
22 |
name='ou', |
|
23 |
), |
|
24 |
migrations.RemoveField( |
|
25 |
model_name='role', |
|
26 |
name='permissions', |
|
27 |
), |
|
28 |
migrations.AlterUniqueTogether( |
|
29 |
name='roleparenting', |
|
30 |
unique_together=None, |
|
31 |
), |
|
32 |
migrations.AlterIndexTogether( |
|
33 |
name='roleparenting', |
|
34 |
index_together=None, |
|
35 |
), |
|
36 |
migrations.RemoveField( |
|
37 |
model_name='roleparenting', |
|
38 |
name='child', |
|
39 |
), |
|
40 |
migrations.RemoveField( |
|
41 |
model_name='roleparenting', |
|
42 |
name='parent', |
|
43 |
), |
|
44 |
migrations.DeleteModel( |
|
45 |
name='Permission', |
|
46 |
), |
|
47 |
migrations.DeleteModel( |
|
48 |
name='Role', |
|
49 |
), |
|
50 |
migrations.DeleteModel( |
|
51 |
name='RoleParenting', |
|
52 |
), |
|
53 |
] |
src/django_rbac/models.py | ||
---|---|---|
1 | 1 |
import functools |
2 |
import hashlib |
|
3 | 2 |
import operator |
4 | 3 | |
5 |
from django.conf import settings |
|
6 | 4 |
from django.contrib import auth |
7 |
from django.contrib.auth import get_user_model |
|
8 | 5 |
from django.contrib.auth.models import Group |
9 | 6 |
from django.contrib.auth.models import Permission as AuthPermission |
10 | 7 |
from django.contrib.auth.models import _user_has_module_perms, _user_has_perm |
... | ... | |
18 | 15 |
return _user_get_permissions(user, obj, 'all') |
19 | 16 | |
20 | 17 | |
21 |
from django.contrib.contenttypes.fields import GenericForeignKey |
|
22 |
from django.contrib.contenttypes.models import ContentType |
|
23 | 18 |
from django.db import models |
24 |
from django.db.models.query import Prefetch, Q |
|
25 |
from django.utils.translation import gettext, pgettext_lazy |
|
19 |
from django.utils.translation import pgettext_lazy |
|
26 | 20 |
from django.utils.translation import ugettext_lazy as _ |
27 |
from model_utils.managers import QueryManager |
|
28 | 21 | |
29 |
from . import backends, constants, managers, utils |
|
30 | ||
31 | ||
32 |
class AbstractBase(models.Model): |
|
33 |
"""Abstract base model for all models having a name and uuid and a |
|
34 |
slug |
|
35 |
""" |
|
36 | ||
37 |
uuid = models.CharField(max_length=32, verbose_name=_('uuid'), unique=True, default=utils.get_hex_uuid) |
|
38 |
name = models.CharField(max_length=256, verbose_name=_('name')) |
|
39 |
slug = models.SlugField(max_length=256, verbose_name=_('slug')) |
|
40 |
description = models.TextField(verbose_name=_('description'), blank=True) |
|
41 | ||
42 |
objects = managers.AbstractBaseManager() |
|
43 | ||
44 |
def __str__(self): |
|
45 |
return str(self.name) |
|
46 | ||
47 |
def __repr__(self): |
|
48 |
return f'<{self.__class__.__name__} {repr(self.slug)} {repr(self.name)}>' |
|
49 | ||
50 |
def save(self, *args, **kwargs): |
|
51 |
# truncate slug and add a hash if it's too long |
|
52 |
if not self.slug: |
|
53 |
self.slug = utils.generate_slug(self.name) |
|
54 |
if len(self.slug) > 256: |
|
55 |
self.slug = self.slug[:252] + hashlib.md5(self.slug).hexdigest()[:4] |
|
56 |
if not self.uuid: |
|
57 |
self.uuid = utils.get_hex_uuid() |
|
58 |
return super().save(*args, **kwargs) |
|
59 | ||
60 |
def natural_key(self): |
|
61 |
return [self.uuid] |
|
62 | ||
63 |
class Meta: |
|
64 |
abstract = True |
|
65 | ||
66 | ||
67 |
class AbstractOrganizationalUnitScopedBase(models.Model): |
|
68 |
'''Base abstract model class for model needing to be scoped by ou''' |
|
69 | ||
70 |
ou = models.ForeignKey( |
|
71 |
to=utils.get_ou_model_name(), |
|
72 |
verbose_name=_('organizational unit'), |
|
73 |
swappable=True, |
|
74 |
blank=True, |
|
75 |
null=True, |
|
76 |
on_delete=models.CASCADE, |
|
77 |
) |
|
78 | ||
79 |
class Meta: |
|
80 |
abstract = True |
|
81 | ||
82 | ||
83 |
class OrganizationalUnitAbstractBase(AbstractBase): |
|
84 |
class Meta: |
|
85 |
abstract = True |
|
86 | ||
87 |
def as_scope(self): |
|
88 |
"""When used as scope to find permissions. Can return a queryset |
|
89 |
in a swapped model if for example your OU are hierarchical. |
|
90 | ||
91 |
Must return an OrganizationalUnit or a queryset. |
|
92 |
""" |
|
93 |
return self |
|
94 | ||
95 | ||
96 |
class OrganizationalUnit(OrganizationalUnitAbstractBase): |
|
97 |
class Meta(OrganizationalUnitAbstractBase.Meta): |
|
98 |
verbose_name = _('organizational unit') |
|
99 |
verbose_name_plural = _('organizational units') |
|
100 |
swappable = constants.RBAC_OU_MODEL_SETTING |
|
22 |
from . import backends, managers |
|
101 | 23 | |
102 | 24 | |
103 | 25 |
class Operation(models.Model): |
... | ... | |
129 | 51 |
Operation._meta.natural_key = ['slug'] |
130 | 52 | |
131 | 53 | |
132 |
class PermissionAbstractBase(models.Model): |
|
133 |
operation = models.ForeignKey(to=Operation, verbose_name=_('operation'), on_delete=models.CASCADE) |
|
134 |
ou = models.ForeignKey( |
|
135 |
to=utils.get_ou_model_name(), |
|
136 |
verbose_name=_('organizational unit'), |
|
137 |
related_name='scoped_permission', |
|
138 |
null=True, |
|
139 |
on_delete=models.CASCADE, |
|
140 |
) |
|
141 |
target_ct = models.ForeignKey(to='contenttypes.ContentType', related_name='+', on_delete=models.CASCADE) |
|
142 |
target_id = models.PositiveIntegerField() |
|
143 |
target = GenericForeignKey('target_ct', 'target_id') |
|
144 | ||
145 |
objects = managers.PermissionManager() |
|
146 | ||
147 |
def natural_key(self): |
|
148 |
return [ |
|
149 |
self.operation.slug, |
|
150 |
self.ou and self.ou.natural_key(), |
|
151 |
self.target and self.target_ct.natural_key(), |
|
152 |
self.target and self.target.natural_key(), |
|
153 |
] |
|
154 | ||
155 |
def export_json(self): |
|
156 |
return { |
|
157 |
"operation": self.operation.natural_key_json(), |
|
158 |
"ou": self.ou and self.ou.natural_key_json(), |
|
159 |
'target_ct': self.target_ct.natural_key_json(), |
|
160 |
"target": self.target.natural_key_json(), |
|
161 |
} |
|
162 | ||
163 |
def __str__(self): |
|
164 |
ct = ContentType.objects.get_for_id(self.target_ct_id) |
|
165 |
ct_ct = ContentType.objects.get_for_model(ContentType) |
|
166 |
if ct == ct_ct: |
|
167 |
target = ContentType.objects.get_for_id(self.target_id) |
|
168 |
s = f'{self.operation} / {target}' |
|
169 |
else: |
|
170 |
s = f'{self.operation} / {ct.name} / {self.target}' |
|
171 |
if self.ou: |
|
172 |
s += gettext(' (scope "{0}")').format(self.ou) |
|
173 |
return s |
|
174 | ||
175 |
class Meta: |
|
176 |
abstract = True |
|
177 |
# FIXME: it's still allow non-unique permission with ou=null |
|
178 |
unique_together = (('operation', 'ou', 'target_ct', 'target_id'),) |
|
179 | ||
180 | ||
181 |
class Permission(PermissionAbstractBase): |
|
182 |
class Meta(PermissionAbstractBase.Meta): |
|
183 |
swappable = constants.RBAC_PERMISSION_MODEL_SETTING |
|
184 |
verbose_name = _('permission') |
|
185 |
verbose_name_plural = _('permissions') |
|
186 | ||
187 | ||
188 |
class RoleAbstractBase(AbstractOrganizationalUnitScopedBase, AbstractBase): |
|
189 |
members = models.ManyToManyField( |
|
190 |
to=settings.AUTH_USER_MODEL, swappable=True, blank=True, related_name='roles' |
|
191 |
) |
|
192 |
permissions = models.ManyToManyField( |
|
193 |
to=utils.get_permission_model_name(), related_name='roles', blank=True |
|
194 |
) |
|
195 | ||
196 |
objects = managers.RoleQuerySet.as_manager() |
|
197 | ||
198 |
def add_child(self, child): |
|
199 |
RoleParenting = utils.get_role_parenting_model() |
|
200 |
RoleParenting.objects.soft_create(self, child) |
|
201 | ||
202 |
def remove_child(self, child): |
|
203 |
RoleParenting = utils.get_role_parenting_model() |
|
204 |
RoleParenting.objects.soft_delete(self, child) |
|
205 | ||
206 |
def add_parent(self, parent): |
|
207 |
RoleParenting = utils.get_role_parenting_model() |
|
208 |
RoleParenting.objects.soft_create(parent, self) |
|
209 | ||
210 |
def remove_parent(self, parent): |
|
211 |
RoleParenting = utils.get_role_parenting_model() |
|
212 |
RoleParenting.objects.soft_delete(parent, self) |
|
213 | ||
214 |
def parents(self, include_self=True, annotate=False, direct=None): |
|
215 |
return self.__class__.objects.filter(pk=self.pk).parents( |
|
216 |
include_self=include_self, annotate=annotate, direct=direct |
|
217 |
) |
|
218 | ||
219 |
def children(self, include_self=True, annotate=False, direct=None): |
|
220 |
return self.__class__.objects.filter(pk=self.pk).children( |
|
221 |
include_self=include_self, |
|
222 |
annotate=annotate, |
|
223 |
direct=direct, |
|
224 |
) |
|
225 | ||
226 |
def all_members(self): |
|
227 |
User = get_user_model() |
|
228 |
prefetch = Prefetch('roles', queryset=self.__class__.objects.filter(pk=self.pk), to_attr='direct') |
|
229 | ||
230 |
return ( |
|
231 |
User.objects.filter( |
|
232 |
Q(roles=self) |
|
233 |
| Q(roles__parent_relation__parent=self) & Q(roles__parent_relation__deleted__isnull=True) |
|
234 |
) |
|
235 |
.distinct() |
|
236 |
.prefetch_related(prefetch) |
|
237 |
) |
|
238 | ||
239 |
def is_direct(self): |
|
240 |
if hasattr(self, 'direct'): |
|
241 |
if self.direct is None: |
|
242 |
return True |
|
243 |
return bool(self.direct) |
|
244 |
return None |
|
245 | ||
246 |
class Meta: |
|
247 |
abstract = True |
|
248 | ||
249 | ||
250 |
class Role(RoleAbstractBase): |
|
251 |
class Meta(RoleAbstractBase.Meta): |
|
252 |
verbose_name = _('role') |
|
253 |
verbose_name_plural = _('roles') |
|
254 |
swappable = constants.RBAC_ROLE_MODEL_SETTING |
|
255 | ||
256 | ||
257 |
class RoleParentingAbstractBase(models.Model): |
|
258 |
parent = models.ForeignKey( |
|
259 |
to=utils.get_role_model_name(), |
|
260 |
swappable=True, |
|
261 |
related_name='child_relation', |
|
262 |
on_delete=models.CASCADE, |
|
263 |
) |
|
264 |
child = models.ForeignKey( |
|
265 |
to=utils.get_role_model_name(), |
|
266 |
swappable=True, |
|
267 |
related_name='parent_relation', |
|
268 |
on_delete=models.CASCADE, |
|
269 |
) |
|
270 |
direct = models.BooleanField(default=True, blank=True) |
|
271 |
created = models.DateTimeField(verbose_name=_('Creation date'), auto_now_add=True) |
|
272 |
deleted = models.DateTimeField(verbose_name=_('Deletion date'), null=True) |
|
273 | ||
274 |
objects = managers.RoleParentingManager() |
|
275 |
alive = QueryManager(deleted__isnull=True) |
|
276 | ||
277 |
def natural_key(self): |
|
278 |
return [self.parent.natural_key(), self.child.natural_key(), self.direct] |
|
279 | ||
280 |
class Meta: |
|
281 |
abstract = True |
|
282 |
unique_together = (('parent', 'child', 'direct'),) |
|
283 |
# covering indexes |
|
284 |
index_together = (('child', 'parent', 'direct'),) |
|
285 | ||
286 | ||
287 |
class RoleParenting(RoleParentingAbstractBase): |
|
288 |
class Meta(RoleParentingAbstractBase.Meta): |
|
289 |
verbose_name = _('role parenting relation') |
|
290 |
verbose_name_plural = _('role parenting relations') |
|
291 |
swappable = constants.RBAC_ROLE_PARENTING_MODEL_SETTING |
|
292 | ||
293 | ||
294 | 54 |
class PermissionMixin(models.Model): |
295 | 55 |
""" |
296 | 56 |
A mixin class that adds the fields and methods necessary to support |
297 |
- |