Files
octopus/myproject/accounts/models.py
Andrey Smakotin b63162b1cb Рефакторинг: убрана зависимость от Django Groups/Permissions для CustomUser
- CustomUser теперь наследуется от AbstractBaseUser (вместо AbstractUser)
- Удалены поля groups и user_permissions из CustomUser
- Все authentication backends (TenantUserBackend, PlatformAdminBackend, RoleBasedPermissionBackend) больше НЕ наследуются от ModelBackend
- Добавлены методы has_perm() и has_module_perms() в CustomUser для делегирования проверки прав кастомным backends
- Полная изоляция: CustomUser использует только систему ролей (UserRole), PlatformAdmin использует только is_superuser
- Удалён весь старый код, связанный с Django permissions
- Нет обратной совместимости (не требуется)
- Чистая архитектура для multi-tenant приложения
2026-01-10 00:10:25 +03:00

157 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import models
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
from django.utils import timezone
import uuid
class CustomUserManager(BaseUserManager):
def create_user(self, email, name, password=None, **extra_fields):
if not email:
raise ValueError('Email обязателен')
email = self.normalize_email(email)
# SECURITY FIX: Явно устанавливаем флаги безопасности в False по умолчанию
# Обычные пользователи НЕ должны иметь доступ к админке
extra_fields.setdefault('is_staff', False)
extra_fields.setdefault('is_superuser', False)
user = self.model(email=email, name=name, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, name, password=None, **extra_fields):
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
extra_fields.setdefault('is_active', True)
# Суперпользователь автоматически имеет подтвержденный email
extra_fields.setdefault('is_email_confirmed', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('Суперпользователь должен иметь is_staff=True.')
if extra_fields.get('is_superuser') is not True:
raise ValueError('Суперпользователь должен иметь is_superuser=True.')
user = self.create_user(email, name, password, **extra_fields)
# Устанавливаем дату подтверждения email
if user.is_email_confirmed and not user.email_confirmed_at:
user.email_confirmed_at = timezone.now()
user.save()
return user
class CustomUser(AbstractBaseUser):
"""
Пользователь тенанта (магазина).
ВАЖНО: Эта модель в TENANT_APPS - каждый тенант имеет свою таблицу!
Один email в разных тенантах = разные записи в разных схемах БД.
Полная изоляция обеспечивается на уровне PostgreSQL schemas.
НЕ является AUTH_USER_MODEL (это PlatformAdmin).
НЕ использует Django Groups/Permissions - используется своя система ролей (UserRole).
"""
email = models.EmailField(unique=True)
name = models.CharField(max_length=100)
# Стандартные поля для совместимости с Django auth
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False) # Для доступа к админке (если нужно)
is_superuser = models.BooleanField(default=False) # Для полных прав в тенанте
date_joined = models.DateTimeField(default=timezone.now)
is_email_confirmed = models.BooleanField(default=False)
email_confirmation_token = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
email_confirmed_at = models.DateTimeField(null=True, blank=True)
password_reset_token = models.UUIDField(null=True, blank=True, editable=False, unique=True)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['name']
objects = CustomUserManager()
class Meta:
verbose_name = "Пользователь магазина"
verbose_name_plural = "Пользователи магазина"
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
"""
Проверка разрешения через authentication backends.
Django вызывает все зарегистрированные backends по очереди.
"""
if not self.is_active:
return False
# Импортируем здесь, чтобы избежать циклических импортов
from django.contrib.auth import get_backends
for backend in get_backends():
if hasattr(backend, 'has_perm'):
result = backend.has_perm(self, perm, obj)
if result is not None: # Backend обработал запрос
return result
return False
def has_module_perms(self, app_label):
"""
Проверка разрешений для модуля через authentication backends.
"""
if not self.is_active:
return False
from django.contrib.auth import get_backends
for backend in get_backends():
if hasattr(backend, 'has_module_perms'):
result = backend.has_module_perms(self, app_label)
if result is not None: # Backend обработал запрос
return result
return False
def generate_confirmation_token(self):
"""Генерирует новый токен для подтверждения email"""
self.email_confirmation_token = uuid.uuid4()
self.save()
return self.email_confirmation_token
def confirm_email(self):
"""Подтверждает email пользователя"""
self.is_email_confirmed = True
self.email_confirmed_at = timezone.now()
self.save()
def get_tenant_role(self):
"""Получить роль пользователя в текущем тенанте"""
from user_roles.services import RoleService
return RoleService.get_user_role(self)
def has_role(self, *role_codes):
"""Проверить, имеет ли пользователь одну из указанных ролей"""
from user_roles.services import RoleService
return RoleService.user_has_role(self, *role_codes)
@property
def is_owner(self):
"""Является ли пользователь владельцем"""
return self.has_role('owner')
@property
def is_manager(self):
"""Является ли пользователь менеджером"""
return self.has_role('manager')
@property
def is_florist(self):
"""Является ли пользователь флористом"""
return self.has_role('florist')
@property
def is_courier(self):
"""Является ли пользователь курьером"""
return self.has_role('courier')