bazis-users 2.2.0__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.
@@ -0,0 +1,31 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ try:
16
+ from importlib.metadata import PackageNotFoundError, version
17
+ __version__ = version('bazis-users')
18
+ except PackageNotFoundError:
19
+ __version__ = 'dev'
20
+
21
+ from django.conf import settings
22
+ from django.contrib.auth import get_user_model
23
+
24
+ from bazis.core.utils.imp import import_class
25
+
26
+
27
+ def get_anonymous_user_model():
28
+ """
29
+ Retrieve the anonymous user model class specified in the Django settings.
30
+ """
31
+ return import_class(settings.AUTH_ANONYMOUS_USER_MODEL)
@@ -0,0 +1,34 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = [
5
+ "__version__",
6
+ "__version_tuple__",
7
+ "version",
8
+ "version_tuple",
9
+ "__commit_id__",
10
+ "commit_id",
11
+ ]
12
+
13
+ TYPE_CHECKING = False
14
+ if TYPE_CHECKING:
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
19
+ COMMIT_ID = Union[str, None]
20
+ else:
21
+ VERSION_TUPLE = object
22
+ COMMIT_ID = object
23
+
24
+ version: str
25
+ __version__: str
26
+ __version_tuple__: VERSION_TUPLE
27
+ version_tuple: VERSION_TUPLE
28
+ commit_id: COMMIT_ID
29
+ __commit_id__: COMMIT_ID
30
+
31
+ __version__ = version = '2.2.0'
32
+ __version_tuple__ = version_tuple = (2, 2, 0)
33
+
34
+ __commit_id__ = commit_id = None
@@ -0,0 +1,40 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from django.utils.translation import gettext_lazy as _
16
+
17
+
18
+ class UserAdminAbstract:
19
+
20
+ def get_fields_common(self, request, obj=None):
21
+ return ('username', 'password')
22
+
23
+ def get_fields_personal(self, request, obj=None):
24
+ return ('first_name', 'last_name', 'email')
25
+
26
+ def get_fields_permissions(self, request, obj=None):
27
+ return ('is_active', 'is_staff', 'is_superuser')
28
+
29
+ def get_fields_dates(self, request, obj=None):
30
+ return ('last_login', 'date_joined', 'dt_first_login')
31
+
32
+ def get_fieldsets(self, request, obj=None):
33
+ if not obj:
34
+ return self.add_fieldsets
35
+ return (
36
+ (None, {'fields': self.get_fields_common(request, obj)}),
37
+ (_('Personal info'), {'fields': self.get_fields_personal(request, obj)}),
38
+ (_('Permissions'), {'fields': self.get_fields_permissions(request, obj)}),
39
+ (_('Important dates'), {'fields': self.get_fields_dates(request, obj)}),
40
+ )
@@ -0,0 +1,28 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from django.utils.translation import gettext_lazy as _
16
+
17
+ from bazis.core.utils.apps import BaseConfig
18
+
19
+
20
+ class UsersConfig(BaseConfig):
21
+ """
22
+ Configuration class for the 'users' application within the Bazis project. It
23
+ sets the application name and provides a human-readable name for the application
24
+ using Django's translation utilities.
25
+ """
26
+
27
+ name = 'bazis.contrib.users'
28
+ verbose_name = _('Users')
@@ -0,0 +1,41 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from django.utils.translation import gettext_lazy as _
16
+
17
+ from pydantic import Field
18
+
19
+ from jose.constants import Algorithms
20
+
21
+ from bazis.core.utils.schemas import BazisSettings
22
+
23
+
24
+ class Settings(BazisSettings):
25
+ """
26
+ Settings class inherits from BazisSettings and defines various configuration
27
+ parameters for the application, including user models, OpenAPI token endpoint,
28
+ JWT algorithm, and JWT session lifetime.
29
+ """
30
+
31
+ AUTH_USER_MODEL: str = Field('users.User', title=_('Default user model'))
32
+ AUTH_ANONYMOUS_USER_MODEL: str = Field(
33
+ 'bazis.contrib.users.models.AnonymousUser', title=_('Default anonymous user model')
34
+ )
35
+ BAZIS_OPENAPI_TOKEN_URL: str = Field('/api/openapi-token/', title=_('OpenAPI token`s endpoint'))
36
+ BAZIS_AUTH_COOKIE_NAME: str = Field('bazis_auth', title=_('Name of the user cookie'))
37
+ BAZIS_JWT_SESSION_ALG: str = Field(Algorithms.HS256, title=_('JWT algorithm'))
38
+ BAZIS_JWT_SESSION_LIFETIME: int = Field(86400, title=_('JWT lifetime'), dynamic=True)
39
+
40
+
41
+ settings = Settings()
@@ -0,0 +1,63 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: PACKAGE VERSION\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2024-07-04 13:45+0000\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "Language: \n"
16
+ "MIME-Version: 1.0\n"
17
+ "Content-Type: text/plain; charset=UTF-8\n"
18
+ "Content-Transfer-Encoding: 8bit\n"
19
+ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
20
+
21
+ #: apps.py:13 router.py:7
22
+ msgid "Users"
23
+ msgstr ""
24
+
25
+ #: conf.py:14
26
+ msgid "Default user model"
27
+ msgstr ""
28
+
29
+ #: conf.py:16
30
+ msgid "Default anonymous user model"
31
+ msgstr ""
32
+
33
+ #: conf.py:18
34
+ msgid "OpenAPI token`s endpoint"
35
+ msgstr ""
36
+
37
+ #: conf.py:19
38
+ msgid "JWT algorithm"
39
+ msgstr ""
40
+
41
+ #: conf.py:20
42
+ msgid "JWT lifetime"
43
+ msgstr ""
44
+
45
+ #: models_abstract.py:30
46
+ msgid "user"
47
+ msgstr ""
48
+
49
+ #: models_abstract.py:31
50
+ msgid "users"
51
+ msgstr ""
52
+
53
+ #: routes.py:30
54
+ msgid "Credentials are invalid"
55
+ msgstr ""
56
+
57
+ #: service.py:31
58
+ msgid "Token expired"
59
+ msgstr ""
60
+
61
+ #: service.py:54
62
+ msgid "Token is invalid"
63
+ msgstr ""
@@ -0,0 +1,81 @@
1
+ # SOME DESCRIPTIVE TITLE.
2
+ # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+ # This file is distributed under the same license as the PACKAGE package.
4
+ # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+ #
6
+ #, fuzzy
7
+ msgid ""
8
+ msgstr ""
9
+ "Project-Id-Version: PACKAGE VERSION\n"
10
+ "Report-Msgid-Bugs-To: \n"
11
+ "POT-Creation-Date: 2025-03-29 00:27+0300\n"
12
+ "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+ "Language-Team: LANGUAGE <LL@li.org>\n"
15
+ "Language: \n"
16
+ "MIME-Version: 1.0\n"
17
+ "Content-Type: text/plain; charset=UTF-8\n"
18
+ "Content-Transfer-Encoding: 8bit\n"
19
+ "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
20
+ "n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
21
+ "(n%100>=11 && n%100<=14)? 2 : 3);\n"
22
+
23
+ #: admin_abstract.py:23
24
+ msgid "Personal info"
25
+ msgstr ""
26
+
27
+ #: admin_abstract.py:24
28
+ msgid "Permissions"
29
+ msgstr ""
30
+
31
+ #: admin_abstract.py:25
32
+ msgid "Important dates"
33
+ msgstr ""
34
+
35
+ #: apps.py:14 router.py:7
36
+ msgid "Users"
37
+ msgstr "Пользователи"
38
+
39
+ #: conf.py:15
40
+ msgid "Default user model"
41
+ msgstr "Модель пользователя по умолчанию"
42
+
43
+ #: conf.py:17
44
+ msgid "Default anonymous user model"
45
+ msgstr "Модель анонимного пользователя по умолчанию"
46
+
47
+ #: conf.py:19
48
+ msgid "OpenAPI token`s endpoint"
49
+ msgstr "Эндпоинт для токена OpenAPI"
50
+
51
+ #: conf.py:20
52
+ msgid "Name of the user cookie"
53
+ msgstr ""
54
+
55
+ #: conf.py:21
56
+ msgid "JWT algorithm"
57
+ msgstr "Алгоритм JWT"
58
+
59
+ #: conf.py:22
60
+ msgid "JWT lifetime"
61
+ msgstr "Срок жизни JWT"
62
+
63
+ #: models_abstract.py:34
64
+ msgid "user"
65
+ msgstr "пользователь"
66
+
67
+ #: models_abstract.py:35
68
+ msgid "users"
69
+ msgstr "Пользователи"
70
+
71
+ #: routes.py:29
72
+ msgid "Credentials are invalid"
73
+ msgstr "Учетные данные недействительны"
74
+
75
+ #: service.py:36
76
+ msgid "Token expired"
77
+ msgstr "Срок действия токена истек"
78
+
79
+ #: service.py:61
80
+ msgid "Token is invalid"
81
+ msgstr "Токен недействителен"
@@ -0,0 +1,37 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from .models_abstract import UserMixin
16
+
17
+
18
+ class UserRequestMiddleware:
19
+ """
20
+ Middleware that attaches the current authenticated user to the context for the
21
+ duration of the request.
22
+ """
23
+
24
+ def __init__(self, get_response):
25
+ """
26
+ Initializes the middleware with the given response handler.
27
+ """
28
+ self.get_response = get_response
29
+
30
+ def __call__(self, request):
31
+ """
32
+ Processes the incoming request, setting the user in the context if the user is
33
+ authenticated, and then passes the request to the next middleware or view.
34
+ """
35
+ if request.user and getattr(request.user, 'is_anonymous', None) is False:
36
+ UserMixin.CTX_USER_REQUEST.set(request.user)
37
+ return self.get_response(request)
@@ -0,0 +1,63 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Generated by Django 5.0.1 on 2024-02-14 15:51
16
+
17
+ import uuid
18
+
19
+ import django.contrib.auth.models
20
+ import django.contrib.auth.validators
21
+ import django.utils.timezone
22
+ from django.db import migrations, models
23
+
24
+ import model_clone.mixin
25
+
26
+
27
+ class Migration(migrations.Migration):
28
+
29
+ initial = True
30
+
31
+ dependencies = [
32
+ ('auth', '0012_alter_user_first_name_max_length'),
33
+ ]
34
+
35
+ operations = [
36
+ migrations.CreateModel(
37
+ name='User',
38
+ fields=[
39
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
40
+ ('password', models.CharField(max_length=128, verbose_name='password')),
41
+ ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
42
+ ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
43
+ ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
44
+ ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
45
+ ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
46
+ ('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
47
+ ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
48
+ ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
49
+ ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
50
+ ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
51
+ ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
52
+ ],
53
+ options={
54
+ 'verbose_name': 'user',
55
+ 'verbose_name_plural': 'users',
56
+ 'abstract': False,
57
+ },
58
+ bases=(model_clone.mixin.CloneMixin, models.Model),
59
+ managers=[
60
+ ('objects', django.contrib.auth.models.UserManager()),
61
+ ],
62
+ ),
63
+ ]
@@ -0,0 +1,34 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ # Generated by Django 6.0.1 on 2026-01-16 17:38
16
+
17
+ from django.db import migrations, models
18
+ from django.utils.translation import gettext_lazy as _
19
+
20
+
21
+
22
+ class Migration(migrations.Migration):
23
+
24
+ dependencies = [
25
+ ('users', '0001_initial'),
26
+ ]
27
+
28
+ operations = [
29
+ migrations.AddField(
30
+ model_name='user',
31
+ name='dt_first_login',
32
+ field=models.DateTimeField(blank=True, null=True, verbose_name='Date/time of first login'),
33
+ ),
34
+ ]
@@ -0,0 +1,14 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
@@ -0,0 +1,34 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from bazis.contrib.users.models_abstract import AnonymousUserAbstract, UserAbstract
16
+ from bazis.core.models_abstract import JsonApiMixin, UuidMixin
17
+
18
+
19
+ class User(JsonApiMixin, UuidMixin, UserAbstract):
20
+ """
21
+ Represents a user in the system, combining JSON API support, UUID functionality,
22
+ and user-specific attributes and methods.
23
+ """
24
+
25
+ pass
26
+
27
+
28
+ class AnonymousUser(AnonymousUserAbstract):
29
+ """
30
+ Represents an anonymous user with attributes and methods specific to users who
31
+ are not logged in.
32
+ """
33
+
34
+ pass
@@ -0,0 +1,156 @@
1
+ # Copyright 2026 EcoFuture Technology Services LLC and contributors
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ The module provides mixins for working with User
17
+ """
18
+
19
+ from contextvars import ContextVar
20
+ from datetime import timedelta
21
+
22
+ from django.conf import settings
23
+ from django.contrib.auth.models import AbstractUser
24
+ from django.contrib.auth.models import AnonymousUser as BaseAnonymousUser
25
+ from django.contrib.auth.signals import user_logged_in
26
+ from django.contrib.gis.db import models
27
+ from django.db import IntegrityError, transaction
28
+ from django.dispatch import receiver
29
+ from django.utils.timezone import now
30
+ from django.utils.translation import gettext_lazy as _
31
+
32
+ from jose import jwt
33
+
34
+ from bazis.core.models_abstract import InitialBase, JsonApiMixin
35
+
36
+
37
+ class UserAbstract(AbstractUser, InitialBase):
38
+ """
39
+ Abstract base class for user models, extending Django's AbstractUser and
40
+ InitialBase.
41
+ """
42
+ dt_first_login = models.DateTimeField('Date/time of first login', blank=True, null=True)
43
+
44
+ class Meta:
45
+ """
46
+ Meta class for UserAbstract, defining verbose names and setting the model as
47
+ abstract.
48
+ """
49
+
50
+ verbose_name = _('user')
51
+ verbose_name_plural = _('users')
52
+ abstract = True
53
+
54
+ def get_full_name(self):
55
+ """
56
+ Returns the full name of the user, combining first and last name if available,
57
+ otherwise returns the username.
58
+ """
59
+ full_name = ''
60
+ if self.first_name:
61
+ full_name = self.first_name
62
+ if self.last_name:
63
+ full_name += ' ' + self.last_name
64
+ return full_name.strip() or self.username
65
+
66
+ def jwt_build(self, auth_type=None) -> str:
67
+ """
68
+ Generates a JSON Web Token (JWT) for the user, optionally including an
69
+ authentication type.
70
+ """
71
+ data = {
72
+ 'sub': self.username,
73
+ 'exp': now() + timedelta(seconds=settings.BAZIS_JWT_SESSION_LIFETIME),
74
+ }
75
+ if auth_type:
76
+ data['auth_type'] = auth_type
77
+
78
+ return jwt.encode(data, settings.SECRET_KEY, algorithm=settings.BAZIS_JWT_SESSION_ALG)
79
+
80
+ @classmethod
81
+ def find_or_create(cls, filters, params):
82
+ """
83
+ Class method to find a user by filters or create a new one with the provided
84
+ parameters, ensuring atomic transactions.
85
+ """
86
+ with transaction.atomic():
87
+ if user := cls.objects.filter(filters).first():
88
+ return user
89
+
90
+ try:
91
+ with transaction.atomic(savepoint=False):
92
+ return cls.objects.create(**params)
93
+ except IntegrityError:
94
+ if user := cls.objects.filter(filters).first():
95
+ return user
96
+ raise
97
+
98
+ @property
99
+ def raw_password(self) -> str | None:
100
+ """
101
+ Property to get the user's password in a masked format or set a new password.
102
+ """
103
+ if self.password:
104
+ return '**********'
105
+
106
+ @raw_password.setter
107
+ def raw_password(self, value):
108
+ """
109
+ Property to get the user's password in a masked format or set a new password.
110
+ """
111
+ self.set_password(value)
112
+
113
+
114
+ @receiver(user_logged_in)
115
+ def set_dt_first_login(sender, user=None, **kwargs):
116
+ if user and not user.dt_first_login and user.pk:
117
+ type(user).objects.filter(pk=user.pk).update(dt_first_login=now())
118
+
119
+
120
+ class AnonymousUserAbstract(BaseAnonymousUser):
121
+ """
122
+ Mixin for the AnonymousUser class, adding basic fields like first_name,
123
+ last_name, and email.
124
+ """
125
+
126
+ first_name = ''
127
+ last_name = ''
128
+ email = ''
129
+
130
+ def get_full_name(self):
131
+ """
132
+ Returns the full name of the anonymous user, combining first and last name if
133
+ available, otherwise returns the username.
134
+ """
135
+ full_name = ''
136
+ if self.first_name:
137
+ full_name = self.first_name
138
+ if self.last_name:
139
+ full_name += ' ' + self.last_name
140
+ return full_name.strip() or self.username
141
+
142
+
143
+ class UserMixin(JsonApiMixin):
144
+ """
145
+ Mixin that allows saving the current user in the context using a ContextVar.
146
+ """
147
+
148
+ #: Context variable in which the current active user can be stored
149
+ CTX_USER_REQUEST: ContextVar['UserMixin'] = ContextVar('CTX_USER_REQUEST', default=None)
150
+
151
+ class Meta:
152
+ """
153
+ Meta class for UserMixin, setting the model as abstract.
154
+ """
155
+
156
+ abstract = True