accrete 0.0.136__py3-none-any.whl → 0.0.139__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/contrib/ui/__init__.py +4 -2
- accrete/contrib/ui/context.py +35 -1
- accrete/contrib/ui/filter.py +7 -3
- accrete/contrib/ui/static/css/.sass-cache/15adf1eed05371361b08787c918a7f18fc15be79/accrete.scssc +0 -0
- accrete/contrib/ui/static/css/accrete.css +8 -0
- accrete/contrib/ui/static/css/accrete.css.map +1 -1
- accrete/contrib/ui/static/css/accrete.scss +12 -3
- accrete/contrib/ui/static/js/alpine-3.14.9.js +5 -0
- accrete/contrib/ui/static/js/alpine-focus-3.14.9.js +15 -0
- accrete/contrib/ui/templates/ui/content_right.html +12 -6
- accrete/contrib/ui/templates/ui/form_error.html +1 -1
- accrete/contrib/ui/templates/ui/layout.html +4 -3
- accrete/contrib/ui/templates/ui/list.html +5 -5
- accrete/contrib/ui/templates/ui/message.html +5 -4
- accrete/contrib/ui/templates/ui/modal.html +7 -7
- accrete/contrib/ui/templates/ui/templatetags/field.html +6 -0
- accrete/contrib/ui/templates/ui/widgets/model_search_select.html +11 -12
- accrete/contrib/ui/templates/ui/widgets/model_search_select_multi.html +13 -12
- accrete/contrib/ui/templates/ui/widgets/model_search_select_options.html +1 -1
- accrete/contrib/ui/templatetags/ui.py +7 -0
- accrete/contrib/ui/utils.py +31 -4
- accrete/contrib/ui/widgets/search_select.py +4 -2
- accrete/contrib/user/admin.py +18 -2
- accrete/contrib/user/auth_backends.py +21 -0
- accrete/contrib/user/migrations/0003_alter_user_email.py +19 -0
- accrete/contrib/user/migrations/0004_user_login_alter_user_email_user_email_or_login_set.py +29 -0
- accrete/contrib/user/migrations/0005_remove_user_email_or_login_set_and_more.py +22 -0
- accrete/contrib/user/migrations/0006_remove_user_email_or_login_set_user_is_managed_and_more.py +31 -0
- accrete/contrib/user/migrations/0007_user_managed_login.py +18 -0
- accrete/contrib/user/migrations/0008_remove_user_no_email_for_managed_user_and_more.py +23 -0
- accrete/contrib/user/models.py +68 -11
- accrete/contrib/user/views.py +7 -2
- accrete/middleware.py +3 -2
- accrete/migrations/0006_alter_member_user.py +21 -0
- accrete/migrations/0007_accessgroup_description.py +18 -0
- accrete/models.py +8 -3
- accrete/tenant.py +10 -6
- accrete/utils/forms.py +2 -2
- accrete/utils/views.py +10 -7
- accrete/views.py +42 -24
- {accrete-0.0.136.dist-info → accrete-0.0.139.dist-info}/METADATA +1 -1
- {accrete-0.0.136.dist-info → accrete-0.0.139.dist-info}/RECORD +45 -34
- accrete/contrib/ui/static/js/alpine3.14.1.js +0 -5
- /accrete/contrib/ui/static/js/{alpine-sort3.14.1.js → alpine-sort-3.14.9.js} +0 -0
- {accrete-0.0.136.dist-info → accrete-0.0.139.dist-info}/WHEEL +0 -0
- {accrete-0.0.136.dist-info → accrete-0.0.139.dist-info}/licenses/LICENSE +0 -0
accrete/contrib/ui/utils.py
CHANGED
@@ -1,17 +1,44 @@
|
|
1
|
+
import ast
|
2
|
+
import json
|
1
3
|
from django.http import HttpRequest, HttpResponse
|
2
4
|
from django.shortcuts import render
|
3
5
|
from .context import ModalContext
|
4
6
|
|
5
7
|
|
6
|
-
def
|
8
|
+
def modal_response(
|
7
9
|
request: HttpRequest,
|
8
10
|
template: str,
|
9
|
-
context: ModalContext | dict
|
11
|
+
context: ModalContext | dict,
|
12
|
+
update: bool = False
|
10
13
|
) -> HttpResponse:
|
11
14
|
|
12
15
|
if isinstance(context, ModalContext):
|
13
16
|
context = context.dict()
|
14
17
|
res = render(request, template, context)
|
15
|
-
|
16
|
-
|
18
|
+
if update:
|
19
|
+
res.headers['HX-Retarget'] = f'#{context["modal_id"]}'
|
20
|
+
res.headers['HX-Reswap'] = 'outerHTML'
|
21
|
+
return res
|
22
|
+
res.headers['HX-Retarget'] = 'body'
|
23
|
+
res.headers['HX-Reswap'] = 'beforeend'
|
17
24
|
return res
|
25
|
+
|
26
|
+
|
27
|
+
def add_trigger(
|
28
|
+
response: [HttpResponse],
|
29
|
+
trigger: dict | str,
|
30
|
+
header: str = 'HX-Trigger'
|
31
|
+
) -> HttpResponse:
|
32
|
+
if isinstance(trigger, str):
|
33
|
+
trigger = {trigger: ''}
|
34
|
+
res_trigger = response.headers.get(header)
|
35
|
+
if not res_trigger:
|
36
|
+
response.headers[header] = json.dumps(trigger)
|
37
|
+
return response
|
38
|
+
try:
|
39
|
+
res_trigger = ast.literal_eval(response.headers.get(header, '{}'))
|
40
|
+
except SyntaxError:
|
41
|
+
res_trigger = {response.headers[header]: ''}
|
42
|
+
res_trigger.update(trigger)
|
43
|
+
response.headers[header] = json.dumps(header)
|
44
|
+
return response
|
@@ -38,7 +38,7 @@ class ModelSearchSelect(widgets.NumberInput):
|
|
38
38
|
'value_display': self.value_display(value),
|
39
39
|
"attrs": self.build_attrs(self.attrs, attrs),
|
40
40
|
"template_name": self.template_name,
|
41
|
-
'search_url': self.search_url,
|
41
|
+
'search_url': resolve_url(self.search_url),
|
42
42
|
'search_parameter': self.search_parameter,
|
43
43
|
'hx_trigger_on_change': self.hx_trigger_on_change,
|
44
44
|
'uuid': uuid
|
@@ -69,6 +69,7 @@ class ModelSearchSelectMulti(widgets.SelectMultiple):
|
|
69
69
|
def __init__(
|
70
70
|
self,
|
71
71
|
search_url: str,
|
72
|
+
search_kwargs: dict = None,
|
72
73
|
search_parameter: str = 'search',
|
73
74
|
limit: int | None = 5,
|
74
75
|
hx_trigger_on_change: bool = False,
|
@@ -76,6 +77,7 @@ class ModelSearchSelectMulti(widgets.SelectMultiple):
|
|
76
77
|
):
|
77
78
|
super().__init__()
|
78
79
|
self.search_url = search_url
|
80
|
+
self.search_kwargs = search_kwargs or {}
|
79
81
|
self.search_parameter = search_parameter
|
80
82
|
self.limit = limit
|
81
83
|
self.hx_trigger_on_change = hx_trigger_on_change
|
@@ -95,7 +97,7 @@ class ModelSearchSelectMulti(widgets.SelectMultiple):
|
|
95
97
|
"value": self.format_value(value),
|
96
98
|
"attrs": self.build_attrs(self.attrs, attrs),
|
97
99
|
"template_name": self.template_name,
|
98
|
-
'search_url': resolve_url(self.search_url),
|
100
|
+
'search_url': resolve_url(self.search_url, **self.search_kwargs),
|
99
101
|
'search_parameter': self.search_parameter,
|
100
102
|
'hx_trigger_on_change': self.hx_trigger_on_change,
|
101
103
|
'uuid': uuid
|
accrete/contrib/user/admin.py
CHANGED
@@ -1,18 +1,33 @@
|
|
1
1
|
from django.contrib import admin
|
2
|
+
from django import forms
|
2
3
|
from .models import User
|
3
4
|
|
4
5
|
|
6
|
+
class UserForm(forms.ModelForm):
|
7
|
+
|
8
|
+
login = forms.CharField(
|
9
|
+
required=False
|
10
|
+
)
|
11
|
+
|
12
|
+
email = forms.EmailField(
|
13
|
+
required=False
|
14
|
+
)
|
15
|
+
|
16
|
+
|
5
17
|
class UserAdmin(admin.ModelAdmin):
|
6
18
|
|
7
19
|
model = User
|
20
|
+
form = UserForm
|
8
21
|
list_display = (
|
9
|
-
'email',
|
10
22
|
'username',
|
23
|
+
'email',
|
24
|
+
'login',
|
11
25
|
'first_name',
|
12
26
|
'last_name'
|
13
27
|
)
|
14
28
|
search_fields = [
|
15
29
|
'email',
|
30
|
+
'login',
|
16
31
|
'username',
|
17
32
|
'first_name',
|
18
33
|
'last_name'
|
@@ -20,7 +35,8 @@ class UserAdmin(admin.ModelAdmin):
|
|
20
35
|
list_filter = [
|
21
36
|
'is_superuser',
|
22
37
|
'is_staff',
|
23
|
-
'is_active'
|
38
|
+
'is_active',
|
39
|
+
'is_managed'
|
24
40
|
]
|
25
41
|
|
26
42
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
from django.contrib.auth import get_user_model
|
2
|
+
from django.contrib.auth.backends import ModelBackend
|
3
|
+
|
4
|
+
User = get_user_model()
|
5
|
+
|
6
|
+
|
7
|
+
class LoginModelBackend(ModelBackend):
|
8
|
+
"""
|
9
|
+
This is a ModelBacked that allows authentication with the login field.
|
10
|
+
"""
|
11
|
+
|
12
|
+
def authenticate(self, request, username=None, password=None, **kwargs):
|
13
|
+
if username is None:
|
14
|
+
return None
|
15
|
+
try:
|
16
|
+
user = User.objects.get(login=username)
|
17
|
+
except User.DoesNotExist:
|
18
|
+
return None
|
19
|
+
if user.check_password(password):
|
20
|
+
return user
|
21
|
+
return None
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Generated by Django 5.1.7 on 2025-03-16 18:11
|
2
|
+
|
3
|
+
import accrete.contrib.user.models
|
4
|
+
from django.db import migrations, models
|
5
|
+
|
6
|
+
|
7
|
+
class Migration(migrations.Migration):
|
8
|
+
|
9
|
+
dependencies = [
|
10
|
+
('user', '0002_user_theme'),
|
11
|
+
]
|
12
|
+
|
13
|
+
operations = [
|
14
|
+
migrations.AlterField(
|
15
|
+
model_name='user',
|
16
|
+
name='email',
|
17
|
+
field=models.EmailField(max_length=254, unique=True, validators=[accrete.contrib.user.models.validate_member_login], verbose_name='Email Address'),
|
18
|
+
),
|
19
|
+
]
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# Generated by Django 5.1.7 on 2025-03-16 19:12
|
2
|
+
|
3
|
+
import accrete.contrib.user.models
|
4
|
+
from django.db import migrations, models
|
5
|
+
|
6
|
+
|
7
|
+
class Migration(migrations.Migration):
|
8
|
+
|
9
|
+
dependencies = [
|
10
|
+
('auth', '0012_alter_user_first_name_max_length'),
|
11
|
+
('user', '0003_alter_user_email'),
|
12
|
+
]
|
13
|
+
|
14
|
+
operations = [
|
15
|
+
migrations.AddField(
|
16
|
+
model_name='user',
|
17
|
+
name='login',
|
18
|
+
field=models.CharField(max_length=254, null=True, unique=True, validators=[accrete.contrib.user.models.validate_member_login], verbose_name='Login'),
|
19
|
+
),
|
20
|
+
migrations.AlterField(
|
21
|
+
model_name='user',
|
22
|
+
name='email',
|
23
|
+
field=models.EmailField(max_length=254, null=True, unique=True, verbose_name='Email Address'),
|
24
|
+
),
|
25
|
+
migrations.AddConstraint(
|
26
|
+
model_name='user',
|
27
|
+
constraint=models.CheckConstraint(condition=models.Q(('email__isnull', False), ('login__isnull', False), _connector='XOR'), name='email_or_login_set', violation_error_message='email or login must be set'),
|
28
|
+
),
|
29
|
+
]
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Generated by Django 5.1.7 on 2025-03-16 19:37
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('auth', '0012_alter_user_first_name_max_length'),
|
10
|
+
('user', '0004_user_login_alter_user_email_user_email_or_login_set'),
|
11
|
+
]
|
12
|
+
|
13
|
+
operations = [
|
14
|
+
migrations.RemoveConstraint(
|
15
|
+
model_name='user',
|
16
|
+
name='email_or_login_set',
|
17
|
+
),
|
18
|
+
migrations.AddConstraint(
|
19
|
+
model_name='user',
|
20
|
+
constraint=models.CheckConstraint(condition=models.Q(('email__isnull', False), ('login__isnull', False), _connector='OR'), name='email_or_login_set', violation_error_message='email or login must be set'),
|
21
|
+
),
|
22
|
+
]
|
accrete/contrib/user/migrations/0006_remove_user_email_or_login_set_user_is_managed_and_more.py
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# Generated by Django 5.1.7 on 2025-03-17 18:09
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('auth', '0012_alter_user_first_name_max_length'),
|
10
|
+
('user', '0005_remove_user_email_or_login_set_and_more'),
|
11
|
+
]
|
12
|
+
|
13
|
+
operations = [
|
14
|
+
migrations.RemoveConstraint(
|
15
|
+
model_name='user',
|
16
|
+
name='email_or_login_set',
|
17
|
+
),
|
18
|
+
migrations.AddField(
|
19
|
+
model_name='user',
|
20
|
+
name='is_managed',
|
21
|
+
field=models.BooleanField(default=False, help_text='User with restricted functionality.', verbose_name='Is Managed'),
|
22
|
+
),
|
23
|
+
migrations.AddConstraint(
|
24
|
+
model_name='user',
|
25
|
+
constraint=models.CheckConstraint(condition=models.Q(('email__isnull', False), ('login__isnull', False), _connector='OR'), name='email_or_login_set', violation_error_message='E-Mail or Login must be set'),
|
26
|
+
),
|
27
|
+
migrations.AddConstraint(
|
28
|
+
model_name='user',
|
29
|
+
constraint=models.CheckConstraint(condition=models.Q(models.Q(('email__isnull', False), ('is_managed', False)), models.Q(('email__isnull', True), ('is_managed', True)), _connector='OR'), name='no_email_for_managed_user', violation_error_message='Managed users must not have an E-Mail address'),
|
30
|
+
),
|
31
|
+
]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Generated by Django 5.1.7 on 2025-03-19 17:53
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('auth', '0012_alter_user_first_name_max_length'),
|
10
|
+
('user', '0006_remove_user_email_or_login_set_user_is_managed_and_more'),
|
11
|
+
]
|
12
|
+
|
13
|
+
operations = [
|
14
|
+
migrations.AddConstraint(
|
15
|
+
model_name='user',
|
16
|
+
constraint=models.CheckConstraint(condition=models.Q(models.Q(('is_managed', True), ('login__isnull', False)), ('is_managed', False), _connector='OR'), name='managed_login', violation_error_message='Managed users must have a login'),
|
17
|
+
),
|
18
|
+
]
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Generated by Django 5.1.7 on 2025-03-23 08:11
|
2
|
+
|
3
|
+
import accrete.contrib.user.models
|
4
|
+
from django.db import migrations, models
|
5
|
+
|
6
|
+
|
7
|
+
class Migration(migrations.Migration):
|
8
|
+
|
9
|
+
dependencies = [
|
10
|
+
('user', '0007_user_managed_login'),
|
11
|
+
]
|
12
|
+
|
13
|
+
operations = [
|
14
|
+
migrations.RemoveConstraint(
|
15
|
+
model_name='user',
|
16
|
+
name='no_email_for_managed_user',
|
17
|
+
),
|
18
|
+
migrations.AlterField(
|
19
|
+
model_name='user',
|
20
|
+
name='language_code',
|
21
|
+
field=models.CharField(blank=True, default=accrete.contrib.user.models.default_language_code, max_length=10, null=True, verbose_name='Language'),
|
22
|
+
),
|
23
|
+
]
|
accrete/contrib/user/models.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
from django.conf import settings
|
2
|
+
from django.core.exceptions import ValidationError
|
2
3
|
from django.db import models
|
3
4
|
from django.contrib.auth.models import (
|
4
5
|
AbstractBaseUser,
|
@@ -6,6 +7,7 @@ from django.contrib.auth.models import (
|
|
6
7
|
)
|
7
8
|
from django.contrib.auth.validators import UnicodeUsernameValidator
|
8
9
|
from django.contrib.auth.models import BaseUserManager
|
10
|
+
from django.db.models import Q
|
9
11
|
from django.utils.translation import gettext_lazy as _
|
10
12
|
from django.utils import timezone
|
11
13
|
from django.shortcuts import resolve_url
|
@@ -18,25 +20,51 @@ LANGUAGE_DISPLAY = {
|
|
18
20
|
}
|
19
21
|
|
20
22
|
|
23
|
+
def validate_member_login(login: str) -> None:
|
24
|
+
if '@' in login:
|
25
|
+
raise ValidationError(_(
|
26
|
+
'Login must not be an E-Mail address, use the field email instead'
|
27
|
+
))
|
28
|
+
message = _(
|
29
|
+
'Login must consist of username and domain seperated by a colon(":")'
|
30
|
+
)
|
31
|
+
if ':' not in login:
|
32
|
+
raise ValidationError(message)
|
33
|
+
member_login, tenant_login = login.split(':', 1)
|
34
|
+
if not member_login or not tenant_login:
|
35
|
+
raise ValidationError(message)
|
36
|
+
|
37
|
+
|
38
|
+
def default_language_code():
|
39
|
+
return settings.LANGUAGE_CODE
|
40
|
+
|
41
|
+
|
21
42
|
class UserManager(BaseUserManager):
|
22
43
|
use_in_migrations = True
|
23
44
|
|
24
|
-
def _create_user(self, email,
|
25
|
-
if not email:
|
26
|
-
raise ValueError('The email must be set')
|
27
|
-
|
28
|
-
|
29
|
-
username
|
30
|
-
|
45
|
+
def _create_user(self, password, email=None, login=None, username=None, **extra_fields):
|
46
|
+
if not email and not login:
|
47
|
+
raise ValueError('The email or login must be set')
|
48
|
+
if not username and login:
|
49
|
+
username = login
|
50
|
+
elif not username and email:
|
51
|
+
username = email
|
52
|
+
user = self.model(**extra_fields)
|
53
|
+
if email:
|
54
|
+
user.email = self.normalize_email(email)
|
55
|
+
if login:
|
56
|
+
user.login = self.model.normalize_username(login)
|
57
|
+
if username:
|
58
|
+
user.username = self.model.normalize_username(username)
|
31
59
|
user.set_password(password)
|
32
60
|
user.save(using=self._db)
|
33
61
|
return user
|
34
62
|
|
35
|
-
def create_user(self, email,
|
63
|
+
def create_user(self, password, email=None, login=None, username=None, **extra_fields):
|
36
64
|
extra_fields.setdefault('is_staff', False)
|
37
65
|
extra_fields.setdefault('is_superuser', False)
|
38
66
|
extra_fields.setdefault('is_active', False)
|
39
|
-
return self._create_user(email,
|
67
|
+
return self._create_user(password, email, login, username, **extra_fields)
|
40
68
|
|
41
69
|
def create_superuser(self, email, password, username=None, **extra_fields):
|
42
70
|
extra_fields.setdefault('is_staff', True)
|
@@ -57,12 +85,25 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|
57
85
|
db_table = 'accrete_user'
|
58
86
|
verbose_name = _('User')
|
59
87
|
verbose_name_plural = _('Users')
|
88
|
+
constraints = [
|
89
|
+
models.CheckConstraint(
|
90
|
+
condition=Q(email__isnull=False) | Q(login__isnull=False),
|
91
|
+
name='email_or_login_set',
|
92
|
+
violation_error_message='E-Mail or Login must be set'
|
93
|
+
),
|
94
|
+
models.CheckConstraint(
|
95
|
+
condition=Q(is_managed=True, login__isnull=False) | Q(is_managed=False),
|
96
|
+
name='managed_login',
|
97
|
+
violation_error_message='Managed users must have a login'
|
98
|
+
)
|
99
|
+
]
|
60
100
|
|
61
101
|
filter_exclude = [
|
62
102
|
'password'
|
63
103
|
]
|
64
104
|
|
65
105
|
username_validator = UnicodeUsernameValidator()
|
106
|
+
login_validator = validate_member_login
|
66
107
|
|
67
108
|
username = models.CharField(
|
68
109
|
verbose_name=_('Username'),
|
@@ -92,7 +133,16 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|
92
133
|
|
93
134
|
email = models.EmailField(
|
94
135
|
verbose_name=_('Email Address'),
|
95
|
-
unique=True
|
136
|
+
unique=True,
|
137
|
+
null=True
|
138
|
+
)
|
139
|
+
|
140
|
+
login = models.CharField(
|
141
|
+
verbose_name=_('Login'),
|
142
|
+
max_length=254,
|
143
|
+
validators=[login_validator],
|
144
|
+
unique=True,
|
145
|
+
null=True
|
96
146
|
)
|
97
147
|
|
98
148
|
is_staff = models.BooleanField(
|
@@ -112,6 +162,12 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|
112
162
|
),
|
113
163
|
)
|
114
164
|
|
165
|
+
is_managed = models.BooleanField(
|
166
|
+
verbose_name=_('Is Managed'),
|
167
|
+
default=False,
|
168
|
+
help_text=_('User with restricted functionality.')
|
169
|
+
)
|
170
|
+
|
115
171
|
date_joined = models.DateTimeField(
|
116
172
|
verbose_name=_('Date Joined'),
|
117
173
|
default=timezone.now
|
@@ -121,7 +177,8 @@ class User(AbstractBaseUser, PermissionsMixin):
|
|
121
177
|
verbose_name=_('Language'),
|
122
178
|
max_length=10,
|
123
179
|
null=True,
|
124
|
-
blank=True
|
180
|
+
blank=True,
|
181
|
+
default=default_language_code
|
125
182
|
)
|
126
183
|
|
127
184
|
theme = models.CharField(
|
accrete/contrib/user/views.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
from django.contrib.auth.forms import AuthenticationForm
|
2
2
|
from django.contrib.auth import views, update_session_auth_hash
|
3
3
|
from django.contrib.auth.decorators import login_required
|
4
|
+
from django.http import HttpResponseForbidden
|
4
5
|
from django.shortcuts import redirect, render, resolve_url
|
5
6
|
from django.utils.translation import gettext_lazy as _
|
6
7
|
from django.conf import settings
|
@@ -58,6 +59,8 @@ def user_detail(request):
|
|
58
59
|
|
59
60
|
@login_required()
|
60
61
|
def user_change_password(request):
|
62
|
+
if request.user.is_managed:
|
63
|
+
return HttpResponseForbidden()
|
61
64
|
form = ChangePasswordForm(instance=request.user)
|
62
65
|
ctx = ui.ModalContext(
|
63
66
|
title=_('Change Password'),
|
@@ -77,12 +80,14 @@ def user_change_password(request):
|
|
77
80
|
+ f'?{request.GET.urlencode()}'
|
78
81
|
)
|
79
82
|
ctx.update(form=form)
|
80
|
-
return ui.
|
83
|
+
return ui.modal_response(request, 'user/change_password.html', ctx)
|
81
84
|
return render(request, 'user/change_password.html', ctx)
|
82
85
|
|
83
86
|
|
84
87
|
@login_required()
|
85
88
|
def user_change_email(request):
|
89
|
+
if request.user.is_managed:
|
90
|
+
return HttpResponseForbidden()
|
86
91
|
form = ChangeEmailForm(instance=request.user)
|
87
92
|
ctx = ui.ModalContext(
|
88
93
|
title=_('Change E-Mail'),
|
@@ -98,5 +103,5 @@ def user_change_email(request):
|
|
98
103
|
if form.is_saved:
|
99
104
|
return redirect('user:detail')
|
100
105
|
ctx.update(form=form)
|
101
|
-
return ui.
|
106
|
+
return ui.modal_response(request, 'user/change_email.html', ctx)
|
102
107
|
return render(request, 'user/change_email.html', ctx)
|
accrete/middleware.py
CHANGED
@@ -39,13 +39,14 @@ class TenantMiddleware(MiddlewareMixin):
|
|
39
39
|
if tenant_id:
|
40
40
|
tenant = Tenant.objects.get(pk=tenant_id)
|
41
41
|
memberships = memberships.filter(tenant=tenant)
|
42
|
-
|
42
|
+
membership_count = memberships.count()
|
43
|
+
if membership_count == 1:
|
43
44
|
request.member = memberships.first()
|
44
45
|
request.tenant = request.member.tenant
|
45
46
|
set_member(request.member)
|
46
47
|
self.update_post_data(request)
|
47
48
|
return
|
48
|
-
if
|
49
|
+
if membership_count > 1:
|
49
50
|
set_member(None)
|
50
51
|
return
|
51
52
|
if request.user.is_staff and tenant:
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Generated by Django 5.1.7 on 2025-03-23 10:21
|
2
|
+
|
3
|
+
import django.db.models.deletion
|
4
|
+
from django.conf import settings
|
5
|
+
from django.db import migrations, models
|
6
|
+
|
7
|
+
|
8
|
+
class Migration(migrations.Migration):
|
9
|
+
|
10
|
+
dependencies = [
|
11
|
+
('accrete', '0005_accessgroup_apply_on_alter_member_access_groups_and_more'),
|
12
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
13
|
+
]
|
14
|
+
|
15
|
+
operations = [
|
16
|
+
migrations.AlterField(
|
17
|
+
model_name='member',
|
18
|
+
name='user',
|
19
|
+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='memberships', to=settings.AUTH_USER_MODEL),
|
20
|
+
),
|
21
|
+
]
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Generated by Django 5.1.7 on 2025-03-31 20:23
|
2
|
+
|
3
|
+
from django.db import migrations, models
|
4
|
+
|
5
|
+
|
6
|
+
class Migration(migrations.Migration):
|
7
|
+
|
8
|
+
dependencies = [
|
9
|
+
('accrete', '0006_alter_member_user'),
|
10
|
+
]
|
11
|
+
|
12
|
+
operations = [
|
13
|
+
migrations.AddField(
|
14
|
+
model_name='accessgroup',
|
15
|
+
name='description',
|
16
|
+
field=models.TextField(blank=True, null=True, verbose_name='Description'),
|
17
|
+
),
|
18
|
+
]
|
accrete/models.py
CHANGED
@@ -90,7 +90,7 @@ class Member(models.Model):
|
|
90
90
|
user = models.ForeignKey(
|
91
91
|
to=settings.AUTH_USER_MODEL,
|
92
92
|
related_name='memberships',
|
93
|
-
on_delete=models.
|
93
|
+
on_delete=models.CASCADE
|
94
94
|
)
|
95
95
|
|
96
96
|
tenant = models.ForeignKey(
|
@@ -136,6 +136,12 @@ class AccessGroup(models.Model):
|
|
136
136
|
max_length=255
|
137
137
|
)
|
138
138
|
|
139
|
+
description = models.TextField(
|
140
|
+
verbose_name=_('Description'),
|
141
|
+
null=True,
|
142
|
+
blank=True
|
143
|
+
)
|
144
|
+
|
139
145
|
code = models.CharField(
|
140
146
|
verbose_name=_('Code'),
|
141
147
|
max_length=100
|
@@ -148,8 +154,7 @@ class AccessGroup(models.Model):
|
|
148
154
|
('tenant', _('Tenant')),
|
149
155
|
('member', _('Member'))
|
150
156
|
],
|
151
|
-
default='tenant'
|
152
|
-
help_text=_('')
|
157
|
+
default='tenant'
|
153
158
|
)
|
154
159
|
|
155
160
|
def __str__(self):
|
accrete/tenant.py
CHANGED
@@ -39,11 +39,6 @@ class Unscoped:
|
|
39
39
|
self.tenant = tenant
|
40
40
|
|
41
41
|
def __enter__(self):
|
42
|
-
if self.tenant is None:
|
43
|
-
_logger.warning(
|
44
|
-
'Entering unscoped context manager with tenant already set to None!',
|
45
|
-
stack_info=True
|
46
|
-
)
|
47
42
|
set_tenant(False)
|
48
43
|
|
49
44
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
@@ -57,7 +52,7 @@ def unscoped():
|
|
57
52
|
def per_tenant(include: Q = None, exclude: Q = None):
|
58
53
|
def decorator(f):
|
59
54
|
def wrapper(*args, **kwargs):
|
60
|
-
tenants
|
55
|
+
tenants = apps.get_model('accrete', 'Tenant').objects.all()
|
61
56
|
if include is not None:
|
62
57
|
tenants = tenants.filter(include)
|
63
58
|
if exclude is not None:
|
@@ -74,6 +69,15 @@ def per_tenant(include: Q = None, exclude: Q = None):
|
|
74
69
|
return decorator
|
75
70
|
|
76
71
|
|
72
|
+
def unscope():
|
73
|
+
def decorator(f):
|
74
|
+
def wrapper(*args, **kwargs):
|
75
|
+
with unscoped():
|
76
|
+
f(*args, **kwargs)
|
77
|
+
return wrapper
|
78
|
+
return decorator
|
79
|
+
|
80
|
+
|
77
81
|
def tenant_has_group(access_group_code: str) -> bool:
|
78
82
|
tenant = get_tenant()
|
79
83
|
if not tenant:
|
accrete/utils/forms.py
CHANGED
@@ -8,7 +8,7 @@ from django.forms import BaseFormSet, Form, ModelForm
|
|
8
8
|
_logger = logging.getLogger(__name__)
|
9
9
|
|
10
10
|
|
11
|
-
def save_form(form: [Form|ModelForm], commit=True, reraise=False) -> [Form|ModelForm]:
|
11
|
+
def save_form(form: [Form|ModelForm], commit=True, reraise=False) -> [Form | ModelForm]:
|
12
12
|
if not hasattr(form, 'save'):
|
13
13
|
raise AttributeError('Form must have method "save" implemented.')
|
14
14
|
form.is_saved = False
|
@@ -30,7 +30,7 @@ def save_form(form: [Form|ModelForm], commit=True, reraise=False) -> [Form|Model
|
|
30
30
|
return form
|
31
31
|
|
32
32
|
|
33
|
-
def save_forms(form, inline_formsets: list = None, commit=True, reraise: bool = False):
|
33
|
+
def save_forms(form, inline_formsets: list = None, commit=True, reraise: bool = False) -> [Form | ModelForm]:
|
34
34
|
|
35
35
|
def handle_error(error):
|
36
36
|
form.save_error = repr(error)
|