Работа с Enum полями

This commit is contained in:
2026-07-30 20:12:39 +03:00
parent abd999d245
commit 50f6638416
26 changed files with 601 additions and 38 deletions

View File

@@ -12,3 +12,5 @@ from .auth_reject_reson import AuthRejectReason
from .http_method import HttpMethod from .http_method import HttpMethod
from .ui_language import UILanguage from .ui_language import UILanguage
from .view_set_action import ViewSetAction from .view_set_action import ViewSetAction
from .workspace_kind import WorkspaceKind
from .workspace_role import WorkspaceRole

View File

@@ -0,0 +1,26 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса WorkspaceKind
"""
from enum import unique
from net.xeaf.rack.core import CoreEnum
@unique
class WorkspaceKind(CoreEnum):
"""
Перечисление Видов пространств
"""
PRIVATE = 'PRIVATE'
""" Частное (личное) пространство """
CORPORATE = 'CORPORATE'
""" Корпоративное пространство """

View File

@@ -0,0 +1,29 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса WorkspaceRole
"""
from enum import unique
from net.xeaf.rack.core import CoreEnum
@unique
class WorkspaceRole(CoreEnum):
"""
Перечисление ролей Аккаунта в рабочем пространстве
"""
OWNER = 'OWNER'
""" Владелец """
ADMIN = 'ADMIN'
""" Администратор """
MEMBER = 'MEMBER'
""" Участник """

View File

@@ -14,6 +14,7 @@ from django.core.management.base import no_translations
from net.xeaf.rack.core import CoreCommand from net.xeaf.rack.core import CoreCommand
from net.xeaf.rack.models.user_story import AccountTestData from net.xeaf.rack.models.user_story import AccountTestData
from net.xeaf.rack.models.user_story import SessionTestData from net.xeaf.rack.models.user_story import SessionTestData
from net.xeaf.rack.models.user_story import WorkspaceTestData
class Command(CoreCommand): class Command(CoreCommand):
@@ -37,6 +38,7 @@ class Command(CoreCommand):
call_command("migrate") call_command("migrate")
# Инициализируем данные # Инициализируем данные
WorkspaceTestData.init_workspaces()
AccountTestData.init_accounts() AccountTestData.init_accounts()
SessionTestData.init_sessions() SessionTestData.init_sessions()

View File

@@ -13,3 +13,4 @@ from .crypto_manager import CryptoManager
from .locale_manager import LocaleManager from .locale_manager import LocaleManager
from .request_manager import RequestManager from .request_manager import RequestManager
from .session_manager import SessionManager from .session_manager import SessionManager
from .workspace_manager import WorkspaceManager

View File

@@ -23,6 +23,7 @@ class AccountManager:
Проверяет возможность авторизации Аккаунта Проверяет возможность авторизации Аккаунта
:param account: Аккаунт :param account: Аккаунт
:return: Причина отказа в авторизации или None, если авторизация возможна :return: Причина отказа в авторизации или None, если авторизация возможна
""" """
@@ -30,7 +31,7 @@ class AccountManager:
return AuthRejectReason.INVALID_USERNAME_OR_PASSWORD return AuthRejectReason.INVALID_USERNAME_OR_PASSWORD
if not account.is_active: if not account.is_active:
return AuthRejectReason.ACCOUNT_IS_NOT_ACTIVE return AuthRejectReason.ACCOUNT_IS_NOT_ACTIVE
if account.joined_at is None: if not account.joined_at:
return AuthRejectReason.REGISTRATION_IS_NOT_COMPLETE return AuthRejectReason.REGISTRATION_IS_NOT_COMPLETE
return None return None
@@ -56,7 +57,7 @@ class AccountManager:
:return: Аккаунт или None, если Аккаунт не найден :return: Аккаунт или None, если Аккаунт не найден
""" """
return AccountModel.objects.filter(username=username).first() return AccountModel.objects.filter(username=username).select_related("workspace").first()
@classmethod @classmethod
def restore_account(cls, account: AccountModel) -> None: def restore_account(cls, account: AccountModel) -> None:

View File

@@ -75,7 +75,7 @@ class SessionManager[T:SessionModel]:
""" """
model = cls._get_session_model_class() model = cls._get_session_model_class()
session = model.objects.filter(token__exact=token).first() session = model.objects.filter(token__exact=token).select_related("account__workspace").first()
if session is not None and not session.is_expired(): if session is not None and not session.is_expired():
return session return session

View File

@@ -0,0 +1,29 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса AccountManager
"""
from net.xeaf.rack.models import WorkspaceModel
class WorkspaceManager:
"""
Реализует методы работы с Рабочими пространствами
"""
@classmethod
def find_by_slug(cls, slug: str) -> WorkspaceModel | None:
"""
Поиск Рабочего пространства по его идентификатору
:param slug: Идентификатор Рабочего пространства
:return: Рабочее пространство или None
"""
return WorkspaceModel.objects.filter(slug=slug).first()

View File

@@ -1,7 +1,8 @@
# Generated by Django 6.0.7 on 2026-07-14 09:26 # Generated by Django 6.0.7 on 2026-07-24 08:25
import django.contrib.auth.models import django.contrib.auth.models
import django.db.models.deletion import django.db.models.deletion
import net.xeaf.rack.enums.workspace_role
import uuid import uuid
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
@@ -16,6 +17,23 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='WorkspaceModel',
fields=[
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='rack.mixin.created_at')),
('deleted_at', models.DateTimeField(blank=True, null=True, verbose_name='rack.mixin.deleted_at')),
('description', models.TextField(blank=True, max_length=2048, null=True, verbose_name='rack.mixin.description')),
('slug', models.SlugField(max_length=36, verbose_name='rack.mixin.slug')),
('updated_at', models.DateTimeField(blank=True, null=True, verbose_name='rack.mixin.updated_at')),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='rack.mixin.id')),
],
options={
'verbose_name': 'rack.models.workspace',
'verbose_name_plural': 'rack.models.workspaces',
'db_table': 'rack_workspaces',
'indexes': [models.Index(fields=['slug'], name='rack_worksp_slug_3aa25b_idx')],
},
),
migrations.CreateModel( migrations.CreateModel(
name='AccountModel', name='AccountModel',
fields=[ fields=[
@@ -34,9 +52,11 @@ class Migration(migrations.Migration):
('full_name', models.CharField(blank=True, max_length=127, null=True, verbose_name='rack.models.account.full_name')), ('full_name', models.CharField(blank=True, max_length=127, null=True, verbose_name='rack.models.account.full_name')),
('joined_at', models.DateTimeField(blank=True, default=None, null=True, verbose_name='rack.models.account.joined_at')), ('joined_at', models.DateTimeField(blank=True, default=None, null=True, verbose_name='rack.models.account.joined_at')),
('last_login', models.DateTimeField(blank=True, default=None, null=True, verbose_name='rack.models.account.last_login')), ('last_login', models.DateTimeField(blank=True, default=None, null=True, verbose_name='rack.models.account.last_login')),
('role', models.CharField(choices=net.xeaf.rack.enums.workspace_role.WorkspaceRole.choices, default=net.xeaf.rack.enums.workspace_role.WorkspaceRole['OWNER'], max_length=12, verbose_name='rack.models.account.role')),
('extra', models.JSONField(default=dict, verbose_name='rack.models.account.extra')), ('extra', models.JSONField(default=dict, verbose_name='rack.models.account.extra')),
('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')), ('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')),
('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')), ('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')),
('workspace', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='net_xeaf_rack.workspacemodel', verbose_name='rack.models.workspace')),
], ],
options={ options={
'verbose_name': 'rack.models.account', 'verbose_name': 'rack.models.account',
@@ -56,29 +76,19 @@ class Migration(migrations.Migration):
('token', models.CharField(max_length=255, verbose_name='rack.models.session_model.token')), ('token', models.CharField(max_length=255, verbose_name='rack.models.session_model.token')),
('device_id', models.CharField(blank=True, default='WEB', max_length=36, null=True, verbose_name='rack.models.session_model.device_id')), ('device_id', models.CharField(blank=True, default='WEB', max_length=36, null=True, verbose_name='rack.models.session_model.device_id')),
('fcm_token', models.CharField(blank=True, default=None, max_length=256, null=True, verbose_name='rack.models.session_model.fcm_token')), ('fcm_token', models.CharField(blank=True, default=None, max_length=256, null=True, verbose_name='rack.models.session_model.fcm_token')),
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='rack.models.session_model.account')), ('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='rack.models.account')),
('actual_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fk_session_model_actual_account', to=settings.AUTH_USER_MODEL, verbose_name='rack.models.session_model.actual_account')), ('actual_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='fk_session_model_actual_account', to=settings.AUTH_USER_MODEL, verbose_name='rack.models.session_model.actual_account')),
], ],
options={ options={
'verbose_name': 'rack.models.session', 'verbose_name': 'rack.models.session',
'verbose_name_plural': 'rack.models.session_plural', 'verbose_name_plural': 'rack.models.session_plural',
'db_table': 'rack_sessions', 'db_table': 'rack_sessions',
'indexes': [models.Index(fields=['account'], name='rack_sessio_account_bd8396_idx'), models.Index(fields=['actual_account'], name='rack_sessio_actual__7e7637_idx')],
'constraints': [models.UniqueConstraint(fields=('token',), name='unique_session_token')],
}, },
), ),
migrations.AddConstraint( migrations.AddConstraint(
model_name='accountmodel', model_name='accountmodel',
constraint=models.UniqueConstraint(fields=('username',), name='unique_account_username'), constraint=models.UniqueConstraint(fields=('username',), name='unique_account_username'),
), ),
migrations.AddIndex(
model_name='sessionmodel',
index=models.Index(fields=['account'], name='rack_sessio_account_bd8396_idx'),
),
migrations.AddIndex(
model_name='sessionmodel',
index=models.Index(fields=['actual_account'], name='rack_sessio_actual__7e7637_idx'),
),
migrations.AddConstraint(
model_name='sessionmodel',
constraint=models.UniqueConstraint(fields=('token',), name='unique_session_token'),
),
] ]

View File

@@ -1,20 +0,0 @@
# Generated by Django 6.0.7 on 2026-07-20 07:59
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('net_xeaf_rack', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='sessionmodel',
name='account',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='rack.models.account'),
),
]

View File

@@ -0,0 +1,19 @@
# Generated by Django 6.0.7 on 2026-07-28 08:29
import net.xeaf.rack.enums.workspace_kind
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('net_xeaf_rack', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='workspacemodel',
name='kind',
field=models.CharField(choices=net.xeaf.rack.enums.workspace_kind.WorkspaceKind.choices, default=net.xeaf.rack.enums.workspace_kind.WorkspaceKind['PRIVATE'], max_length=16, verbose_name='rack.models.workspace_model.kind'),
),
]

View File

@@ -10,3 +10,4 @@
from .account_model import AccountModel from .account_model import AccountModel
from .session_model import SessionModel from .session_model import SessionModel
from .workspace_model import WorkspaceModel

View File

@@ -12,12 +12,13 @@ from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from net.xeaf.rack.enums import WorkspaceRole
from net.xeaf.rack.models.mixins import CreatedAtMixin from net.xeaf.rack.models.mixins import CreatedAtMixin
from net.xeaf.rack.models.mixins import DeletedAtMixin from net.xeaf.rack.models.mixins import DeletedAtMixin
from net.xeaf.rack.models.mixins import DescriptionMixin from net.xeaf.rack.models.mixins import DescriptionMixin
from net.xeaf.rack.models.mixins import UpdatedAtMixin from net.xeaf.rack.models.mixins import UpdatedAtMixin
from net.xeaf.rack.models.mixins import UuidIdMixin from net.xeaf.rack.models.mixins import UuidIdMixin
from .workspace_model import WorkspaceModel
class AccountModel(AbstractUser, UuidIdMixin, CreatedAtMixin, UpdatedAtMixin, DeletedAtMixin, DescriptionMixin, ): class AccountModel(AbstractUser, UuidIdMixin, CreatedAtMixin, UpdatedAtMixin, DeletedAtMixin, DescriptionMixin, ):
""" """
@@ -73,6 +74,25 @@ class AccountModel(AbstractUser, UuidIdMixin, CreatedAtMixin, UpdatedAtMixin, De
) )
""" Дата и время последнего входа """ """ Дата и время последнего входа """
workspace = models.ForeignKey(
WorkspaceModel,
on_delete=models.CASCADE,
null=False,
blank=False,
verbose_name=_("rack.models.workspace"),
)
""" Рабочее пространство пользователя """
role = models.CharField(
choices=WorkspaceRole.choices,
default=WorkspaceRole.OWNER,
null=False,
max_length=12,
blank=False,
verbose_name=_("rack.models.account.role"),
)
""" Роль пользователя """
extra = models.JSONField( extra = models.JSONField(
null=False, null=False,
default=dict, default=dict,

View File

@@ -0,0 +1,12 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Пакет описания классов полей моделей данных
"""
from .enum_choice_field import EnumChoiceField
from .extensible_enum_choice_field import ExtensibleEnumChoiceField

View File

@@ -0,0 +1,146 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса EnumChoiceField
"""
from typing import Any
from typing import Sequence
from typing import Type
from django.core.exceptions import ValidationError
from django.db import models
from net.xeaf.rack.core.core_enum import CoreEnum
class EnumChoiceField(models.CharField):
"""
Поле выбора перечисления со строгой рантайм-валидацией типов
"""
MAX_DEFAULT_LENGTH = 64
""" Максимальный размер поля """
_enum_cls: Type[CoreEnum]
""" Класс перечисления """
def __init__(self, choices: Type[CoreEnum], *args, **kwargs):
"""
Инициализация
:param choices: Класс перечисления CoreEnum
:param args: Дополнительные аргументы
:param kwargs: Дополнительные параметры
"""
if not isinstance(choices, type) or not issubclass(choices, CoreEnum):
raise TypeError("The 'choices' parameter must be a subclass of CoreEnum.")
self._enum_cls = choices
kwargs.setdefault('max_length', self.MAX_DEFAULT_LENGTH)
kwargs['choices'] = choices.choices()
super().__init__(*args, **kwargs)
# noinspection unused-parameter
def from_db_value[E: CoreEnum](self, value: Any, expression: Any, connection: Any) -> E | str | None:
"""
Преобразует сырое значение из базы данных в объект CoreEnum
:param value: Входящее сырое значение
:param expression: Выражение для преобразования значения
:param connection: Соединение с базой данных
:return: Представление объекта CoreEnum или строка, или None
"""
return self._normalize_and_validate(value, action_context="read")
def to_python[E: CoreEnum](self, value: Any) -> E | str | None:
"""
Строго преобразует входящее значение в объект CoreEnum при очистке форм или сериализации
:param value: Входящее значение
:return: Объект CoreEnum или строка, или None
"""
return self._normalize_and_validate(value, action_context="clean")
def validate(self, value: Any, model_instance: Any) -> None:
"""
Валидация значения поля
"""
if value is None or value == "":
return super().validate(value, model_instance)
lookup_value = value.value if isinstance(value, self._enum_cls) else value
if lookup_value not in self._enum_cls.values():
raise ValidationError(
self.error_messages['invalid_choice'],
code='invalid_choice',
params={'value': value},
)
return super(models.CharField, self).validate(value, model_instance)
def get_prep_value(self, value: Any) -> str | None:
"""
Преобразует объект CoreEnum обратно в строку перед сохранением в БД
"""
# Стандартная проверка
value = super().get_prep_value(value)
# Проверяем строгость типов и ограничений через наш метод
resolved = self._normalize_and_validate(value, action_context="save")
if isinstance(resolved, self._enum_cls):
return resolved.value
return resolved
def deconstruct(self) -> tuple[str, str, Sequence[Any], dict[str, Any]]:
"""
Возвращает параметры для сериализации поля
:return: Кортеж с параметрами для сериализации поля
"""
name, path, args, kwargs = super().deconstruct()
kwargs['choices'] = self._enum_cls
return name, path, args, kwargs
def _normalize_and_validate(self, value: Any, action_context: str) -> CoreEnum | str | None:
"""
Внутренний метод для проверки null/blank ограничений и приведения значения к CoreEnum.
Реализует паттерн Fail-Fast для предотвращения работы с некорректными данными.
:param value: Входящее сырое значение
:param action_context: Контекст выполнения для текста ошибки (e.g., 'read', 'save')
:return: Объект CoreEnum, пустая строка или None
:raises ValueError: Если значение нарушает правила null/blank или отсутствует в перечислении
"""
if value is None:
if not self.null:
raise ValueError(f"Cannot {action_context} null; the field '{self.name}' is not nullable.")
return None
if value == "":
if not self.blank:
raise ValueError(f"Cannot {action_context} blank string; the field '{self.name}' does not allow blanks.")
return ""
if isinstance(value, self._enum_cls):
return value
return self._enum_cls.from_string(str(value))

View File

@@ -0,0 +1,156 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса ExtensibleEnumChoiceField
"""
from typing import Any
from typing import Sequence
from typing import Type
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models
from net.xeaf.rack.core.core_enum import CoreEnum
class ExtensibleEnumChoiceField(models.CharField):
"""
Конфигурируемое поле выбора перечисления, класс которого
полностью настраивается пользователем через settings.py.
"""
MAX_DEFAULT_LENGTH = 64
""" Максимальный размер поля по умолчанию """
_settings_key: str
""" Ключ в settings.py для получения замещающего класса перечисления """
_active_enum_cls: Type[CoreEnum]
""" Активный в текущем рантайме класс перечисления """
def __init__(self, choices: str, *args: Any, **kwargs: Any) -> None:
"""
Инициализация
"""
if not isinstance(choices, str):
raise TypeError(
f"The 'choices' parameter in {self.__class__.__name__} must be a string containing "
f"the settings key name, got {type(choices).__name__} instead."
)
self._settings_key = choices
user_enum = getattr(settings, self._settings_key, None)
if user_enum is None:
raise ValueError(
f"The required configuration setting '{self._settings_key}' "
f"is missing or not set in your settings.py!"
)
if not isinstance(user_enum, type) or not issubclass(user_enum, CoreEnum):
raise TypeError(
f"The '{self._settings_key}' setting in settings.py "
f"must be a subclass of CoreEnum to maintain strict typing."
)
self._active_enum_cls = user_enum
kwargs.setdefault('max_length', self.MAX_DEFAULT_LENGTH)
kwargs['choices'] = self._active_enum_cls.choices()
super().__init__(*args, **kwargs)
def _normalize_and_validate(self, value: Any, action_context: str) -> CoreEnum | str | None:
"""
Внутренний метод для проверки ограничений и приведения значения к активному классу CoreEnum.
"""
if value is None:
if not self.null:
raise ValueError(f"Cannot {action_context} null; the field '{self.name}' is not nullable.")
return None
if value == "":
if not self.blank:
raise ValueError(f"Cannot {action_context} blank string; the field '{self.name}' does not allow blanks.")
return ""
if isinstance(value, CoreEnum):
if isinstance(value, self._active_enum_cls):
return value
str_val = value.value
else:
str_val = str(value)
try:
return self._active_enum_cls.from_string(str_val)
except ValueError:
raise ValueError(
f"Cannot {action_context} value '{value}'; it cannot be resolved by active "
f"enum class '{self._active_enum_cls.__name__}' configured via '{self._settings_key}'."
)
# noinspection unused-parameter
def from_db_value[E: CoreEnum](self, value: Any, expression: Any, connection: Any) -> E | str | None:
"""
Преобразует сырое значение из базы данных в объект замещенного перечисления
"""
return self._normalize_and_validate(value, action_context="read")
def to_python[E: CoreEnum](self, value: Any) -> E | str | None:
"""
Строго преобразует входящее значение в объект замещенного перечисления
"""
return self._normalize_and_validate(value, action_context="clean")
def validate(self, value: Any, model_instance: Any) -> None:
"""
Валидация значения поля по замещенному списку
"""
if value is None or value == "":
return super().validate(value, model_instance)
lookup_value = value.value if isinstance(value, CoreEnum) else value
if lookup_value not in self._active_enum_cls.values():
raise ValidationError(
self.error_messages['invalid_choice'],
code='invalid_choice',
params={'value': value},
)
return super().validate(value, model_instance)
def get_prep_value(self, value: Any) -> str | None:
"""
Преобразует объект CoreEnum обратно в строку перед сохранением в БД
"""
value = super().get_prep_value(value)
resolved = self._normalize_and_validate(value, action_context="save")
if isinstance(resolved, self._active_enum_cls):
return resolved.value
return resolved
def deconstruct(self) -> tuple[str, str, Sequence[Any], dict[str, Any]]:
"""
Возвращает параметры для сериализации поля
:return: Кортеж с параметрами для сериализации поля
"""
name, path, args, kwargs = super().deconstruct()
kwargs['choices'] = self._settings_key
return name, path, args, kwargs

View File

@@ -55,6 +55,7 @@ class JournalModel(CoreModel, CreatedAtMixin):
""" """
Параметры конфигурации класса Параметры конфигурации класса
""" """
# Индексы для производительности # Индексы для производительности
indexes = [ indexes = [
models.Index(fields=["account"]), models.Index(fields=["account"]),

View File

@@ -10,3 +10,4 @@
from .account_test_data import AccountTestData from .account_test_data import AccountTestData
from .session_test_data import SessionTestData from .session_test_data import SessionTestData
from .workspace_test_data import WorkspaceTestData

View File

@@ -10,8 +10,10 @@
import uuid import uuid
from net.xeaf.rack.enums import WorkspaceRole
from net.xeaf.rack.models import AccountModel from net.xeaf.rack.models import AccountModel
from net.xeaf.rack.utils import DateUtils from net.xeaf.rack.utils import DateUtils
from .workspace_test_data import WorkspaceTestData
class AccountTestData: class AccountTestData:
@@ -56,6 +58,8 @@ class AccountTestData:
account.full_name = "Иван И. Иванов" account.full_name = "Иван И. Иванов"
account.email = "i.ivanov@xeaf.net" account.email = "i.ivanov@xeaf.net"
account.set_password(cls.DEFAULT_PASSWORD) account.set_password(cls.DEFAULT_PASSWORD)
account.workspace_id = WorkspaceTestData.IVANOV_WORKSPACE_ID
account.role = WorkspaceRole.OWNER
account.joined_at = DateUtils.now() account.joined_at = DateUtils.now()
account.save() account.save()
@@ -72,6 +76,8 @@ class AccountTestData:
account.full_name = "Петр П. Петров" account.full_name = "Петр П. Петров"
account.email = "p.petrov@xeaf.net" account.email = "p.petrov@xeaf.net"
account.set_password(cls.DEFAULT_PASSWORD) account.set_password(cls.DEFAULT_PASSWORD)
account.workspace_id = WorkspaceTestData.PETROV_WORKSPACE_ID
account.role = WorkspaceRole.OWNER
account.joined_at = DateUtils.now() account.joined_at = DateUtils.now()
account.save() account.save()

View File

@@ -0,0 +1,64 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса WorkspaceTestData
"""
import uuid
from net.xeaf.rack.models import WorkspaceModel
class WorkspaceTestData:
"""
Инициализирует модели данных тестовых Рабочих пространств
"""
IVANOV_WORKSPACE_ID = uuid.UUID("377cbf24-6658-4b51-a7b2-7ca1ca0f331d")
""" Уникальный идентификатор пространства пользователя Иван Иванов """
IVANOV_WORKSPACE = 'i-ivanov'
""" Имя пространства пользователя Иван Иванов """
PETROV_WORKSPACE_ID = uuid.UUID("22711511-a3c2-4891-88c3-68c14272f949")
""" Уникальный идентификатор пространства пользователя Петр Петров """
PETROV_WORKSPACE = 'p-petrov'
""" Имя пространства пользователя Петр Петров """
@classmethod
def init_workspaces(cls):
"""
Инициализирует модели данных тестовых Рабочих пространств
"""
cls.init_ivanov_workspace()
cls.init_petrov_workspace()
@classmethod
def init_ivanov_workspace(cls):
"""
Инициализирует модель данных тестового пространства пользователя Иван Иванов
"""
workspace = WorkspaceModel()
workspace.id = cls.IVANOV_WORKSPACE_ID
workspace.slug = cls.IVANOV_WORKSPACE
workspace.description = "Пространство пользователя Иван Иванов"
workspace.save()
@classmethod
def init_petrov_workspace(cls):
"""
Инициализирует модель данных тестового пространства пользователя Петр Петров
"""
workspace = WorkspaceModel()
workspace.id = cls.PETROV_WORKSPACE_ID
workspace.slug = cls.PETROV_WORKSPACE
workspace.description = "Пространство пользователя Петр Петров"
workspace.save()

View File

@@ -0,0 +1,47 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Описание класса Session
"""
from django.db import models
from django.utils.translation import gettext_lazy as _
from net.xeaf.rack.core import CorePersistentModel
from net.xeaf.rack.enums import WorkspaceKind
from net.xeaf.rack.models.mixins import DescriptionMixin
from net.xeaf.rack.models.mixins import SlugMixin
class WorkspaceModel(CorePersistentModel, SlugMixin, DescriptionMixin):
"""
Модель рабочего пространства
"""
kind = models.CharField(
max_length=16,
choices=WorkspaceKind.choices,
default=WorkspaceKind.PRIVATE,
null=False,
blank=False,
verbose_name=_("rack.models.workspace_model.kind")
)
""" Вид пространства """
class Meta:
"""
Параметры конфигурации класса
"""
# Индексы для производительности
indexes = [
models.Index(fields=["slug"]),
]
db_table = "rack_workspaces"
verbose_name = _("rack.models.workspace")
verbose_name_plural = _("rack.models.workspaces")

View File

@@ -0,0 +1,10 @@
# DRF Rack
# Библиотека классов расширений для Django REST Framework
#
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
# Все права защищены. Лицензия: MIT
"""
Пакет описания классов сериализаторов авторизации
"""