Добавлены классы моделей данных Account, Session
This commit is contained in:
@@ -10,4 +10,8 @@
|
|||||||
|
|
||||||
from .core_enum import CoreEnum
|
from .core_enum import CoreEnum
|
||||||
from .core_exception import CoreException
|
from .core_exception import CoreException
|
||||||
|
from .core_model import CoreModel
|
||||||
|
from .core_model import CoreModel
|
||||||
|
from .core_persistent_model import CorePersistentModel
|
||||||
|
from .core_persistent_model import CorePersistentModel
|
||||||
from .core_response import CoreResponse
|
from .core_response import CoreResponse
|
||||||
|
|||||||
26
net/xeaf/rack/core/core_model.py
Normal file
26
net/xeaf/rack/core/core_model.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание класса CoreModel
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
from net.xeaf.rack.models.mixins import UuidIdMixin
|
||||||
|
|
||||||
|
|
||||||
|
class CoreModel(UuidIdMixin, models.Model):
|
||||||
|
"""
|
||||||
|
Базовый класс для всех классов моделей данных проекта
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Параметры конфигурации класса
|
||||||
|
"""
|
||||||
|
|
||||||
|
abstract = True
|
||||||
27
net/xeaf/rack/core/core_persistent_model.py
Normal file
27
net/xeaf/rack/core/core_persistent_model.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание класса CorePersistentModel
|
||||||
|
"""
|
||||||
|
|
||||||
|
from net.xeaf.rack.models.mixins import CreatedAtMixin
|
||||||
|
from net.xeaf.rack.models.mixins import DeletedAtMixin
|
||||||
|
from net.xeaf.rack.models.mixins import UpdatedAtMixin
|
||||||
|
from .core_model import CoreModel
|
||||||
|
|
||||||
|
|
||||||
|
class CorePersistentModel(CoreModel, CreatedAtMixin, DeletedAtMixin, UpdatedAtMixin):
|
||||||
|
"""
|
||||||
|
Базовый класс для всех классов моделей данных с постоянным хранением
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Параметры конфигурации класса
|
||||||
|
"""
|
||||||
|
|
||||||
|
abstract = True
|
||||||
84
net/xeaf/rack/migrations/0001_initial.py
Normal file
84
net/xeaf/rack/migrations/0001_initial.py
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# Generated by Django 6.0.7 on 2026-07-12 09:19
|
||||||
|
|
||||||
|
import django.contrib.auth.models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('auth', '0012_alter_user_first_name_max_length'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='AccountModel',
|
||||||
|
fields=[
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
|
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
||||||
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
|
('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')),
|
||||||
|
('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')),
|
||||||
|
('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')),
|
||||||
|
('username', models.EmailField(default=None, max_length=254, verbose_name='rack.models.account.username')),
|
||||||
|
('nick_name', models.CharField(blank=True, max_length=32, null=True, verbose_name='rack.models.account.nick_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')),
|
||||||
|
('last_login', models.DateTimeField(blank=True, default=None, null=True, verbose_name='rack.models.account.last_login')),
|
||||||
|
('extra', models.JSONField(default={}, 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')),
|
||||||
|
('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')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'rack.models.account',
|
||||||
|
'verbose_name_plural': 'rack.models.account_plural',
|
||||||
|
'db_table': 'rack_accounts',
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SessionModel',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='rack.mixin.created_at')),
|
||||||
|
('expires_at', models.DateTimeField(verbose_name='rack.mixin.expires_at')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False, verbose_name='rack.mixin.id')),
|
||||||
|
('token', models.CharField(max_length=32, 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')),
|
||||||
|
('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')),
|
||||||
|
('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={
|
||||||
|
'verbose_name': 'rack.models.session',
|
||||||
|
'verbose_name_plural': 'rack.models.session_plural',
|
||||||
|
'db_table': 'rack_sessions',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='accountmodel',
|
||||||
|
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'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -7,3 +7,6 @@
|
|||||||
"""
|
"""
|
||||||
Пакет описания классов моделей данных
|
Пакет описания классов моделей данных
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .account_model import AccountModel
|
||||||
|
from .session_model import SessionModel
|
||||||
|
|||||||
116
net/xeaf/rack/models/account_model.py
Normal file
116
net/xeaf/rack/models/account_model.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание класса Account
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from net.xeaf.rack.models.mixins import CreatedAtMixin
|
||||||
|
from net.xeaf.rack.models.mixins import DeletedAtMixin
|
||||||
|
from net.xeaf.rack.models.mixins import DescriptionMixin
|
||||||
|
from net.xeaf.rack.models.mixins import UpdatedAtMixin
|
||||||
|
from net.xeaf.rack.models.mixins import UuidIdMixin
|
||||||
|
|
||||||
|
|
||||||
|
class AccountModel(AbstractUser, UuidIdMixin, CreatedAtMixin, UpdatedAtMixin, DeletedAtMixin, DescriptionMixin, ):
|
||||||
|
"""
|
||||||
|
Модель Аккаунта пользователя
|
||||||
|
"""
|
||||||
|
|
||||||
|
first_name = None
|
||||||
|
""" Удаляем поле Имя """
|
||||||
|
|
||||||
|
last_name = None
|
||||||
|
""" Удаляем поле Фамилия """
|
||||||
|
|
||||||
|
date_joined = None
|
||||||
|
""" Удаляем поле Дата регистрации """
|
||||||
|
|
||||||
|
username = models.EmailField(
|
||||||
|
null=False,
|
||||||
|
default=None,
|
||||||
|
blank=False,
|
||||||
|
verbose_name=_("rack.models.account.username"),
|
||||||
|
)
|
||||||
|
""" Имя пользователя """
|
||||||
|
|
||||||
|
nick_name = models.CharField(
|
||||||
|
null=True,
|
||||||
|
max_length=32,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("rack.models.account.nick_name"),
|
||||||
|
)
|
||||||
|
""" Псевдоним """
|
||||||
|
|
||||||
|
full_name = models.CharField(
|
||||||
|
null=True,
|
||||||
|
max_length=127,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("rack.models.account.full_name"),
|
||||||
|
)
|
||||||
|
""" Полное имя пользователя """
|
||||||
|
|
||||||
|
joined_at = models.DateTimeField(
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("rack.models.account.joined_at"),
|
||||||
|
)
|
||||||
|
""" Дата и время завершения регистрации """
|
||||||
|
|
||||||
|
last_login = models.DateTimeField(
|
||||||
|
null=True,
|
||||||
|
default=None,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("rack.models.account.last_login"),
|
||||||
|
)
|
||||||
|
""" Дата и время последнего входа """
|
||||||
|
|
||||||
|
extra = models.JSONField(
|
||||||
|
null=False,
|
||||||
|
default={},
|
||||||
|
blank=False,
|
||||||
|
verbose_name=_("rack.models.account.extra"),
|
||||||
|
)
|
||||||
|
""" Дополнительная информация """
|
||||||
|
|
||||||
|
def get_full_name(self):
|
||||||
|
"""
|
||||||
|
Возвращает полное имя пользователя,
|
||||||
|
требуется для совместимости
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.full_name
|
||||||
|
|
||||||
|
def get_short_name(self):
|
||||||
|
"""
|
||||||
|
Возвращает краткое имя пользователя
|
||||||
|
требуется для совместимости
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.nick_name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Параметры конфигурации класса
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Уникальность
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["username"],
|
||||||
|
name="unique_account_username"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Данные таблицы
|
||||||
|
db_table = "rack_accounts"
|
||||||
|
verbose_name = _("rack.models.account")
|
||||||
|
verbose_name_plural = _("rack.models.account_plural")
|
||||||
93
net/xeaf/rack/models/session_model.py
Normal file
93
net/xeaf/rack/models/session_model.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание класса Session
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from net.xeaf.rack.core import CoreModel
|
||||||
|
from net.xeaf.rack.managers import CryptoManager
|
||||||
|
from net.xeaf.rack.models.mixins import CreatedAtMixin
|
||||||
|
from net.xeaf.rack.models.mixins import ExpiresAtMixin
|
||||||
|
|
||||||
|
|
||||||
|
class SessionModel(CoreModel, CreatedAtMixin, ExpiresAtMixin):
|
||||||
|
"""
|
||||||
|
Модель сессии пользователя
|
||||||
|
"""
|
||||||
|
|
||||||
|
token = models.CharField(
|
||||||
|
max_length=CryptoManager.DEFAULT_TOKEN_LENGTH,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
verbose_name=_("rack.models.session_model.token"),
|
||||||
|
)
|
||||||
|
""" Токен сессии """
|
||||||
|
|
||||||
|
account = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
verbose_name=_("rack.models.session_model.account"),
|
||||||
|
)
|
||||||
|
""" Аккаунт пользователя """
|
||||||
|
|
||||||
|
actual_account = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
null=False,
|
||||||
|
blank=False,
|
||||||
|
related_name="fk_session_model_actual_account",
|
||||||
|
verbose_name=_("rack.models.session_model.actual_account"),
|
||||||
|
)
|
||||||
|
""" Актуальный аккаунт пользователя """
|
||||||
|
|
||||||
|
device_id = models.CharField(
|
||||||
|
max_length=36,
|
||||||
|
default='WEB',
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("rack.models.session_model.device_id"),
|
||||||
|
)
|
||||||
|
""" Идентификатор устройства """
|
||||||
|
|
||||||
|
fcm_token = models.CharField(
|
||||||
|
max_length=256,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name=_("rack.models.session_model.fcm_token"),
|
||||||
|
)
|
||||||
|
""" Токен для подключения к FCM """
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""
|
||||||
|
Параметры конфигурации класса
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Уникальность
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(
|
||||||
|
fields=["token"],
|
||||||
|
name="unique_session_token"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Индексы для производительности
|
||||||
|
indexes = [
|
||||||
|
models.Index(fields=["account"]),
|
||||||
|
models.Index(fields=["actual_account"]),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Данные таблицы
|
||||||
|
db_table = "rack_sessions"
|
||||||
|
verbose_name = _("rack.models.session")
|
||||||
|
verbose_name_plural = _("rack.models.session_plural")
|
||||||
@@ -7,3 +7,6 @@
|
|||||||
"""
|
"""
|
||||||
Пакет описания тестов классов моделей данных
|
Пакет описания тестов классов моделей данных
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from .account_model_tests import AccountModelTests
|
||||||
|
from .session_model_tests import SessionModelTests
|
||||||
|
|||||||
72
net/xeaf/rack/tests/models/account_model_tests.py
Normal file
72
net/xeaf/rack/tests/models/account_model_tests.py
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание класса AccountModelTests
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from net.xeaf.rack.models import AccountModel
|
||||||
|
|
||||||
|
|
||||||
|
class AccountModelTests(TestCase):
|
||||||
|
"""
|
||||||
|
Тесты для класса AccountModel
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_create_account_with_defaults(self):
|
||||||
|
"""
|
||||||
|
Проверяет корректное создание аккаунта с дефолтными полями
|
||||||
|
"""
|
||||||
|
|
||||||
|
email = "test_user@example.com"
|
||||||
|
|
||||||
|
# Создаем через чистый ORM, как ты любишь, построчно
|
||||||
|
account = AccountModel()
|
||||||
|
account.username = email
|
||||||
|
account.save()
|
||||||
|
|
||||||
|
# Проверяем контракт полей
|
||||||
|
self.assertEqual(account.username, email)
|
||||||
|
self.assertIsNone(account.full_name)
|
||||||
|
self.assertIsNone(account.nick_name)
|
||||||
|
|
||||||
|
# Если ты исправил дефолт на dict, тест гарантирует безопасность:
|
||||||
|
self.assertEqual(account.extra, {})
|
||||||
|
|
||||||
|
def test_compatibility_methods(self):
|
||||||
|
"""
|
||||||
|
Проверяет методы совместимости get_full_name и get_short_name
|
||||||
|
"""
|
||||||
|
|
||||||
|
account = AccountModel()
|
||||||
|
account.username = "names@example.com"
|
||||||
|
account.full_name = "Иван Иванов"
|
||||||
|
account.nick_name = "vanya"
|
||||||
|
account.save()
|
||||||
|
|
||||||
|
self.assertEqual(account.get_full_name(), "Иван Иванов")
|
||||||
|
self.assertEqual(account.get_short_name(), "vanya")
|
||||||
|
|
||||||
|
def test_username_uniqueness_constraint(self):
|
||||||
|
"""
|
||||||
|
Проверяет жесткое ограничение уникальности на уровне UniqueConstraint
|
||||||
|
"""
|
||||||
|
|
||||||
|
duplicate_email = "duplicate@example.com"
|
||||||
|
|
||||||
|
# Создаем первого
|
||||||
|
account1 = AccountModel(username=duplicate_email)
|
||||||
|
account1.save()
|
||||||
|
|
||||||
|
# Попытка создать второго с тем же username должна намертво упасть в БД
|
||||||
|
account2 = AccountModel(username=duplicate_email)
|
||||||
|
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
account2.save()
|
||||||
|
|
||||||
83
net/xeaf/rack/tests/models/session_model_tests.py
Normal file
83
net/xeaf/rack/tests/models/session_model_tests.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# DRF Rack
|
||||||
|
# Библиотека классов расширений для Django REST Framework
|
||||||
|
#
|
||||||
|
# Автор: Николай В. Анохин <n.anokhin@xeaf.net>
|
||||||
|
# Все права защищены. Лицензия: MIT
|
||||||
|
|
||||||
|
"""
|
||||||
|
Описание класса SessionModelTests
|
||||||
|
"""
|
||||||
|
|
||||||
|
from django.db.utils import IntegrityError
|
||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
from net.xeaf.rack.managers import CryptoManager
|
||||||
|
from net.xeaf.rack.models import AccountModel
|
||||||
|
from net.xeaf.rack.models import SessionModel
|
||||||
|
|
||||||
|
|
||||||
|
class SessionModelTests(TestCase):
|
||||||
|
"""
|
||||||
|
Тесты для класса SessionModel
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Атомарно создаем тестовых пользователей для связей
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.primary_user = AccountModel.objects.create_user(
|
||||||
|
username="primary@example.com",
|
||||||
|
password="secure_password_123"
|
||||||
|
)
|
||||||
|
self.actual_user = AccountModel.objects.create_user(
|
||||||
|
username="actual@example.com",
|
||||||
|
password="secure_password_456"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_create_session_with_defaults_and_relations(self):
|
||||||
|
"""
|
||||||
|
Проверяет успешное создание сессии, заполнение дефолтных полей и внешних ключей
|
||||||
|
"""
|
||||||
|
|
||||||
|
token_str = CryptoManager.gen_token()
|
||||||
|
|
||||||
|
session = SessionModel()
|
||||||
|
session.token = token_str
|
||||||
|
session.account = self.primary_user
|
||||||
|
session.actual_account = self.actual_user
|
||||||
|
session.renew(3600)
|
||||||
|
session.save()
|
||||||
|
|
||||||
|
# Проверяем контракт полей и связей
|
||||||
|
self.assertEqual(session.token, token_str)
|
||||||
|
self.assertEqual(session.account.id, self.primary_user.id)
|
||||||
|
self.assertEqual(session.actual_account.id, self.actual_user.id)
|
||||||
|
self.assertEqual(session.device_id, "WEB")
|
||||||
|
|
||||||
|
def test_token_uniqueness_constraint(self):
|
||||||
|
"""
|
||||||
|
Проверяет жесткое ограничение уникальности токена сессии в базе данных
|
||||||
|
"""
|
||||||
|
|
||||||
|
duplicate_token = "same_token_xyz"
|
||||||
|
|
||||||
|
# Создаем первую валидную сессию
|
||||||
|
session1 = SessionModel(
|
||||||
|
token=duplicate_token,
|
||||||
|
account=self.primary_user,
|
||||||
|
actual_account=self.actual_user
|
||||||
|
)
|
||||||
|
session1.renew(3600)
|
||||||
|
session1.save()
|
||||||
|
|
||||||
|
# Попытка создать вторую сессию с тем же токеном должна намертво упасть в БД
|
||||||
|
session2 = SessionModel(
|
||||||
|
token=duplicate_token,
|
||||||
|
account=self.primary_user,
|
||||||
|
actual_account=self.actual_user
|
||||||
|
)
|
||||||
|
|
||||||
|
with self.assertRaises(IntegrityError):
|
||||||
|
session2.renew(3600)
|
||||||
|
session2.save()
|
||||||
@@ -99,6 +99,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
AUTH_USER_MODEL = 'net_xeaf_rack.AccountModel'
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/6.0/topics/i18n/
|
# https://docs.djangoproject.com/en/6.0/topics/i18n/
|
||||||
|
|||||||
Reference in New Issue
Block a user