accrete 0.0.123__py3-none-any.whl → 0.0.125__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
accrete/admin.py CHANGED
@@ -9,12 +9,12 @@ class MemberInLine(admin.TabularInline):
9
9
 
10
10
  class TenantAccessGroupInLine(admin.TabularInline):
11
11
 
12
- model = models.TenantAccessGroup
12
+ model = models.TenantAccessGroupRel
13
13
 
14
14
 
15
15
  class AccessGroupMemberInLine(admin.TabularInline):
16
16
 
17
- model = models.AccessGroupMember
17
+ model = models.MemberAccessGroupRel
18
18
 
19
19
 
20
20
  class TenantAdmin(admin.ModelAdmin):
@@ -46,4 +46,5 @@ class AccessGroupAdmin(admin.ModelAdmin):
46
46
  admin.site.register(models.Tenant, TenantAdmin)
47
47
  admin.site.register(models.Member, MemberAdmin)
48
48
  admin.site.register(models.AccessGroup, AccessGroupAdmin)
49
- admin.site.register(models.AccessGroupMember)
49
+ admin.site.register(models.MemberAccessGroupRel)
50
+ admin.site.register(models.TenantAccessGroupRel)
accrete/config.py CHANGED
@@ -8,6 +8,11 @@ ACCRETE_TENANT_NOT_SET_URL = getattr(
8
8
  False
9
9
  )
10
10
 
11
+ ACCRETE_GROUP_NOT_SET_URL = getattr(
12
+ settings, 'ACCRETE_GROUP_NOT_SET_URL',
13
+ False
14
+ )
15
+
11
16
  if not ACCRETE_TENANT_NOT_SET_URL:
12
17
  _logger.warning(
13
18
  'Setting ACCRETE_TENANT_NOT_SET_URL missing.'
@@ -0,0 +1,17 @@
1
+ # Generated by Django 5.1.4 on 2025-02-19 18:25
2
+
3
+ from django.db import migrations
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('country', '0002_auto_20250219_1759'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.AlterModelOptions(
14
+ name='country',
15
+ options={'ordering': ['-order_priority', 'name'], 'verbose_name': 'Country', 'verbose_name_plural': 'Countries'},
16
+ ),
17
+ ]
@@ -1,5 +1,4 @@
1
1
  from django.db import models
2
- from django.db.models.functions import Lower
3
2
  from django.utils.translation import gettext_lazy as _
4
3
  from accrete.fields import TranslatedCharField
5
4
 
@@ -10,7 +9,7 @@ class Country(models.Model):
10
9
  verbose_name = _('Country')
11
10
  verbose_name_plural = _('Countries')
12
11
  db_table = 'accrete_country'
13
- ordering = ['-order_priority', Lower('name')]
12
+ ordering = ['-order_priority', 'name']
14
13
 
15
14
  name = TranslatedCharField(
16
15
  verbose_name=_('Name')
@@ -0,0 +1,42 @@
1
+ # Generated by Django 5.1.4 on 2025-02-24 17:54
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+
8
+ dependencies = [
9
+ ('accrete', '0003_remove_member_name'),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RenameModel(
14
+ old_name='AccessGroupMember',
15
+ new_name='MemberAccessGroupRel',
16
+ ),
17
+ migrations.RenameModel(
18
+ old_name='TenantAccessGroup',
19
+ new_name='TenantAccessGroupRel',
20
+ ),
21
+ migrations.AlterModelOptions(
22
+ name='memberaccessgrouprel',
23
+ options={'ordering': ['member'], 'verbose_name': 'Member Access Group Relation', 'verbose_name_plural': 'Member Access Group Relations'},
24
+ ),
25
+ migrations.AlterModelOptions(
26
+ name='tenantaccessgrouprel',
27
+ options={'ordering': ['tenant'], 'verbose_name': 'Tenant Access Group Relation', 'verbose_name_plural': 'Tenant Access Groups Relations'},
28
+ ),
29
+ migrations.RemoveField(
30
+ model_name='accessgroup',
31
+ name='is_public',
32
+ ),
33
+ migrations.AlterField(
34
+ model_name='accessgroup',
35
+ name='code',
36
+ field=models.CharField(max_length=100, verbose_name='Code'),
37
+ ),
38
+ migrations.AlterModelTable(
39
+ name='memberaccessgrouprel',
40
+ table='accrete_member_access_group_rel',
41
+ ),
42
+ ]
accrete/models.py CHANGED
@@ -1,7 +1,6 @@
1
1
  from django.db import models
2
2
  from django.conf import settings
3
3
  from django.utils.translation import gettext_lazy as _
4
- from django.contrib.auth.validators import UnicodeUsernameValidator
5
4
  from accrete.tenant import get_tenant
6
5
  from accrete.managers import TenantManager, MemberManager
7
6
 
@@ -70,7 +69,7 @@ class Tenant(models.Model):
70
69
 
71
70
  access_groups = models.ManyToManyField(
72
71
  to='accrete.AccessGroup',
73
- through='accrete.TenantAccessGroup',
72
+ through='accrete.TenantAccessGroupRel',
74
73
  through_fields=('tenant', 'access_group')
75
74
  )
76
75
 
@@ -105,7 +104,7 @@ class Member(models.Model):
105
104
 
106
105
  access_groups = models.ManyToManyField(
107
106
  to='accrete.AccessGroup',
108
- through='accrete.AccessGroupMember',
107
+ through='accrete.MemberAccessGroupRel',
109
108
  through_fields=('member', 'access_group')
110
109
  )
111
110
 
@@ -136,28 +135,20 @@ class AccessGroup(models.Model):
136
135
 
137
136
  code = models.CharField(
138
137
  verbose_name=_('Code'),
139
- max_length=50
140
- )
141
-
142
- is_public = models.BooleanField(
143
- verbose_name=_('Is Public'),
144
- default=True,
145
- help_text=_(
146
- 'If set, members from all tenants can be assigned to this group.'
147
- )
138
+ max_length=100
148
139
  )
149
140
 
150
141
  def __str__(self):
151
142
  return self.name
152
143
 
153
144
 
154
- class AccessGroupMember(models.Model):
145
+ class MemberAccessGroupRel(models.Model):
155
146
 
156
147
  class Meta:
157
- verbose_name = _('Access Group Member')
158
- verbose_name_plural = _('Access Group Members')
148
+ verbose_name = _('Member Access Group Relation')
149
+ verbose_name_plural = _('Member Access Group Relations')
159
150
  ordering = ['member']
160
- db_table = 'accrete_access_group_member_rel'
151
+ db_table = 'accrete_member_access_group_rel'
161
152
  constraints = [
162
153
  models.UniqueConstraint(
163
154
  name='unique_member_per_group',
@@ -179,11 +170,11 @@ class AccessGroupMember(models.Model):
179
170
  return f'{self.member} - {self.access_group}'
180
171
 
181
172
 
182
- class TenantAccessGroup(models.Model):
173
+ class TenantAccessGroupRel(models.Model):
183
174
 
184
175
  class Meta:
185
- verbose_name = _('Tenant Access Group')
186
- verbose_name_plural = _('Tenant Access Groups')
176
+ verbose_name = _('Tenant Access Group Relation')
177
+ verbose_name_plural = _('Tenant Access Groups Relations')
187
178
  ordering = ['tenant']
188
179
  db_table = 'accrete_tenant_access_group_rel'
189
180
  constraints = [
accrete/tenant.py CHANGED
@@ -73,3 +73,16 @@ def per_tenant(include: Q = None, exclude: Q = None):
73
73
  return wrapper
74
74
  return decorator
75
75
 
76
+
77
+ def tenant_has_group(access_group_code: str) -> bool:
78
+ tenant = get_tenant()
79
+ if not tenant:
80
+ return False
81
+ return tenant.access_groups.filter(code__in=[access_group_code]).exists()
82
+
83
+
84
+ def member_has_group(access_group_code: str) -> bool:
85
+ member = get_member()
86
+ if not member:
87
+ return False
88
+ return member.access_groups.filter(code__in=[access_group_code]).exists()
accrete/views.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from enum import Enum
2
3
  from functools import wraps
3
4
  from django.http import HttpResponse, HttpResponseNotFound
4
5
  from django.contrib.auth.mixins import LoginRequiredMixin
@@ -7,24 +8,43 @@ from django.core.exceptions import ImproperlyConfigured
7
8
  from django.shortcuts import redirect, get_object_or_404
8
9
  from django.conf import settings
9
10
  from accrete.models import Tenant, Member
10
- from accrete.tenant import get_tenant
11
+ from accrete.tenant import get_tenant, tenant_has_group, member_has_group
11
12
  from . import config
12
13
 
13
14
 
15
+ class GroupType(Enum):
16
+
17
+ TENANT: str = 'tenant'
18
+ MEMBER: str = 'member'
19
+
20
+
14
21
  class TenantRequiredMixin(LoginRequiredMixin):
15
22
 
16
23
  TENANT_NOT_SET_URL = None
24
+ GROUP_NOT_SET_URL = None
25
+ TENANT_GROUPS = []
26
+ MEMBER_GROUPS = []
17
27
 
18
28
  def dispatch(self, request, *args, **kwargs):
19
29
  res = super().dispatch(request, *args, **kwargs)
20
30
  tenant = self.get_tenant()
21
31
  if not tenant:
22
32
  return self.handle_tenant_not_set()
33
+ if not self.check_tenant_group():
34
+ return self.handle_tenant_group_not_set()
35
+ if not self.check_member_group():
36
+ return self.handle_member_group_not_set()
23
37
  return res
24
38
 
25
39
  def handle_tenant_not_set(self):
26
40
  return redirect(self.get_tenant_not_set_url())
27
41
 
42
+ def handle_tenant_group_not_set(self):
43
+ return redirect(self.get_group_not_set_url(GroupType.TENANT))
44
+
45
+ def handle_member_group_not_set(self):
46
+ return redirect(self.get_group_not_set_url(GroupType.MEMBER))
47
+
28
48
  def get_tenant_not_set_url(self):
29
49
  tenant_not_set_url = (
30
50
  self.TENANT_NOT_SET_URL
@@ -40,12 +60,45 @@ class TenantRequiredMixin(LoginRequiredMixin):
40
60
  )
41
61
  return tenant_not_set_url
42
62
 
63
+ def get_group_not_set_url(self, group_type: GroupType):
64
+ group_not_set_url = (
65
+ self.GROUP_NOT_SET_URL
66
+ or settings.ACCRETE_GROUP_NOT_SET_URL
67
+ )
68
+ if not group_not_set_url:
69
+ cls_name = self.__class__.__name__
70
+ raise ImproperlyConfigured(
71
+ f"{cls_name} is missing the group_not_set_url attribute. "
72
+ f"Define {cls_name}.GROUP_NOT_SET_URL, "
73
+ f"settings.ACCRETE_GROUP_NOT_SET_URL, or override "
74
+ f"{cls_name}.get_group_not_set_url()."
75
+ )
76
+ return group_not_set_url
77
+
78
+ def check_tenant_group(self) -> bool:
79
+ if not self.TENANT_GROUPS:
80
+ return True
81
+ for group in self.TENANT_GROUPS:
82
+ if tenant_has_group(group):
83
+ return True
84
+ return False
85
+
86
+ def check_member_group(self) -> bool:
87
+ if not self.MEMBER_GROUPS:
88
+ return True
89
+ for group in self.MEMBER_GROUPS:
90
+ if member_has_group(group):
91
+ return True
92
+ return False
93
+
43
94
  @staticmethod
44
95
  def get_tenant():
45
96
  return get_tenant()
46
97
 
47
98
 
48
99
  def tenant_required(
100
+ tenant_groups: list[str] = None,
101
+ member_groups: list[str] = None,
49
102
  redirect_field_name: str = None,
50
103
  login_url: str = None
51
104
  ):
@@ -59,6 +112,12 @@ def tenant_required(
59
112
  tenant = request.tenant
60
113
  if not tenant:
61
114
  return redirect(config.ACCRETE_TENANT_NOT_SET_URL)
115
+ for tenant_group in (tenant_groups or []):
116
+ if not any([tenant_has_group(tenant_group)]):
117
+ return redirect(config.ACCRETE_GROUP_NOT_SET_URL)
118
+ for member_group in (member_groups or []):
119
+ if not any([member_has_group(member_group)]):
120
+ return redirect(config.ACCRETE_GROUP_NOT_SET_URL)
62
121
  return f(request, *args, **kwargs)
63
122
  return _wrapped_view
64
123
  return decorator
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: accrete
3
- Version: 0.0.123
3
+ Version: 0.0.125
4
4
  Summary: Django Shared Schema Multi Tenant
5
5
  Author-email: Benedikt Jilek <benedikt.jilek@pm.me>
6
6
  License: Copyright (c) 2023 Benedikt Jilek
@@ -1,24 +1,25 @@
1
1
  accrete/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- accrete/admin.py,sha256=MUYUmCFlGYPowiXTbwl4_Q6Cq0-neiL53WW4P76JCLs,1174
2
+ accrete/admin.py,sha256=6NiX8_3pYIhnru5hNzvnW4xfzWsbf7gZj8Z2p38xIOk,1232
3
3
  accrete/apps.py,sha256=F7ynMLHJr_6bRujWtZVUzCliY2CGKiDvyUmL4F68L2E,146
4
- accrete/config.py,sha256=eJUbvyBO3DvAD6xkVKjTAzlXy7V7EK9bVyb91girfUs,299
4
+ accrete/config.py,sha256=1Yubvz5PVdCsX0tA7HvazhtnvCvgCoEl33_dR8SHpb8,392
5
5
  accrete/fields.py,sha256=9SlltB5AJvDfiAbYGWZemrqpjqDl1XNgNrhyTGoBJ2A,4693
6
6
  accrete/forms.py,sha256=H2hPQemslRLvTVV0Wl1TfUmTc5wU3Z98nQTMiLMliqo,1288
7
7
  accrete/managers.py,sha256=DevRVm7cStvlfz6TriitSINr40POCi4HNaHX48VkrMA,1620
8
8
  accrete/middleware.py,sha256=YN73WloNkN01oel9Dcj3xyhurcWoB6zMV0NT3hY8DGw,2264
9
- accrete/models.py,sha256=k6BLNeQY4H_-WiODLpMoNJp_g3Dm4sTfeNj5n68oJ88,5109
9
+ accrete/models.py,sha256=01UPWHmkWg3CcgPjImaSvS7OPh1Xx7lQ1nsJxZNN53k,4879
10
10
  accrete/storage.py,sha256=Jp3oE_uPMqgarjS_G49KDFrR2eSe4XuIJK9oAF_QBxk,1288
11
- accrete/tenant.py,sha256=-__rFtYrc5SbxFX2-Dvw6Wc1XzSAD6DeIBRuOVQlbWQ,1908
11
+ accrete/tenant.py,sha256=GGKEhKLsC6JAkNGrMvp4l8ScsyfrDMocwJL5LBPe2Go,2307
12
12
  accrete/tests.py,sha256=Agltbzwwh5htvq_Qi9vqvxutzmg_GwgPS_N19xJZRlw,7197
13
13
  accrete/urls.py,sha256=goDFR-yhOlLLy7AMi9pmh2aBkxdtZtwXNg6mwI2zPhU,227
14
- accrete/views.py,sha256=dwgXLkIjqjG16vlG4C6QeS191HoDnT7bZtkLahJB6wQ,2582
14
+ accrete/views.py,sha256=NEDX3yrEGjLe_dP47-B3Y537FvJCwEOBqcPrGS3lfuY,4752
15
15
  accrete/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  accrete/contrib/country/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  accrete/contrib/country/admin.py,sha256=0dAcFPfC8c80fhKKOL26-5Wl1uWXBYrkUJEjo2sEkk4,329
18
18
  accrete/contrib/country/apps.py,sha256=95KSUCEezHIvXmxGXCST7Ag1xvpREHQGy2mIJWWx9R0,162
19
- accrete/contrib/country/models.py,sha256=waflXa6oCVowzFQmhySQohzRmcjB10mv25EehCqDR-U,983
19
+ accrete/contrib/country/models.py,sha256=yEAr-hiqEYcx9nfQRKNl5L-u00Dcm0HtJOiTBAyZbO4,931
20
20
  accrete/contrib/country/migrations/0001_initial.py,sha256=M-wKIa9gFtkZHatg-m-LRvrUrEZcKnenRtxLu7NMLbQ,1261
21
21
  accrete/contrib/country/migrations/0002_auto_20250219_1759.py,sha256=hSP0WEErHHJWCYTYvJSI00JWzH1iEcobNLR_4n8Ygq8,24841
22
+ accrete/contrib/country/migrations/0003_alter_country_options.py,sha256=0reOOdHuApMA5pw4ga6BW63TCG2U1i8T8oSug7EjUXw,428
22
23
  accrete/contrib/country/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
24
  accrete/contrib/log/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
25
  accrete/contrib/log/admin.py,sha256=xeuLk1GdU_M0rZR8SQUzcMeWvV9ud0hz_LJmaut5eJE,1211
@@ -250,6 +251,7 @@ accrete/contrib/user_registration/templates/user_registration/mail_templates/con
250
251
  accrete/migrations/0001_initial.py,sha256=azThbc8otEhxJwo8BIgOt5eC30mxXhKJLBAazZFe3BA,4166
251
252
  accrete/migrations/0002_initial.py,sha256=dFOM7kdHlx7pVAh8cTDlZMtciN4O9Z547HAzEKnygZE,1628
252
253
  accrete/migrations/0003_remove_member_name.py,sha256=bnZrzOIXcqsoGfbqgohTN5OHm2IldnLlBz1HNJDeqKc,315
254
+ accrete/migrations/0004_rename_accessgroupmember_memberaccessgrouprel_and_more.py,sha256=NXEKuRyIjLFXqycWB1jIZFQ0ppevMwz1I6rAr9qKrk4,1404
253
255
  accrete/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
254
256
  accrete/utils/__init__.py,sha256=saw9zi2XItJOPbv4fjTXOpl7StNtC803jHhapFcGx08,312
255
257
  accrete/utils/dates.py,sha256=apM6kt6JhGrKgoT0jfav1W-8AUVTxNc9xt3fJQ2n0JI,1492
@@ -257,7 +259,7 @@ accrete/utils/forms.py,sha256=IvxbXNpSd4a-JBgsTJhs2GHe-DCRWX-xnVPRcoiCzbI,3104
257
259
  accrete/utils/log.py,sha256=BH0MBDweAjx30wGBO4F3sFhbgkSoEs7T1lLLjlYZNnA,407
258
260
  accrete/utils/models.py,sha256=2xTacvcpmDK_Bp4rAK7JdVLf8HU009LYNJ6eSpMgYZI,1014
259
261
  accrete/utils/views.py,sha256=AutijWetWGgjdO1PNc4gxCblT-i1fAfldNDFRbO9Sac,5012
260
- accrete-0.0.123.dist-info/METADATA,sha256=wUGZk9TrXWGL3Hnzb4GofGgl-cM2DQ4g1leMMPOsQhM,4953
261
- accrete-0.0.123.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
262
- accrete-0.0.123.dist-info/licenses/LICENSE,sha256=_7laeMIHnsd3Y2vJEXDYXq_PEXxIcjgJsGt8UIKTRWc,1057
263
- accrete-0.0.123.dist-info/RECORD,,
262
+ accrete-0.0.125.dist-info/METADATA,sha256=ydqTjC-yrBSis0uMo7-3fD1ua8earnP0149gN6iZrlA,4953
263
+ accrete-0.0.125.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
264
+ accrete-0.0.125.dist-info/licenses/LICENSE,sha256=_7laeMIHnsd3Y2vJEXDYXq_PEXxIcjgJsGt8UIKTRWc,1057
265
+ accrete-0.0.125.dist-info/RECORD,,