feat(core): add core module with mixins, services, and background jobs
- Add Model Mixins: TimestampMixin, SoftDeleteMixin, AuditMixin, etc. - Add Base Services: BaseService, BulkOperationsMixin, QueryOptimizerMixin - Add Base ViewSets with bulk operations - Add BackgroundJob model for Celery task tracking - Add BaseAppCommand for management commands - Add permissions, pagination, filters, cache, logging - Migrate tests to factory_boy + faker - Add CHANGELOG.md - 297 tests passing
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any
|
||||
|
||||
from apps.core.exceptions import NotFoundError
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import transaction
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
@@ -38,23 +39,53 @@ class UserService:
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def get_user_by_email(cls, email: str) -> Optional[User]:
|
||||
"""Получает пользователя по email"""
|
||||
def get_user_by_email(cls, email: str) -> User:
|
||||
"""Получает пользователя по email
|
||||
|
||||
Raises:
|
||||
NotFoundError: Если пользователь не найден
|
||||
"""
|
||||
try:
|
||||
return User.objects.get(email=email)
|
||||
except User.DoesNotExist as e:
|
||||
raise NotFoundError(
|
||||
message=f"User with email={email} not found",
|
||||
details={"email": email},
|
||||
) from e
|
||||
|
||||
@classmethod
|
||||
def get_user_by_email_or_none(cls, email: str) -> User | None:
|
||||
"""Получает пользователя по email или None"""
|
||||
try:
|
||||
return User.objects.get(email=email)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_user_by_id(cls, user_id: int) -> Optional[User]:
|
||||
"""Получает пользователя по ID"""
|
||||
def get_user_by_id(cls, user_id: int) -> User:
|
||||
"""Получает пользователя по ID
|
||||
|
||||
Raises:
|
||||
NotFoundError: Если пользователь не найден
|
||||
"""
|
||||
try:
|
||||
return User.objects.get(id=user_id)
|
||||
except User.DoesNotExist as e:
|
||||
raise NotFoundError(
|
||||
message=f"User with id={user_id} not found",
|
||||
details={"user_id": user_id},
|
||||
) from e
|
||||
|
||||
@classmethod
|
||||
def get_user_by_id_or_none(cls, user_id: int) -> User | None:
|
||||
"""Получает пользователя по ID или None"""
|
||||
try:
|
||||
return User.objects.get(id=user_id)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def update_user(cls, user_id: int, **fields) -> Optional[User]:
|
||||
def update_user(cls, user_id: int, **fields) -> User:
|
||||
"""
|
||||
Обновляет данные пользователя
|
||||
|
||||
@@ -63,11 +94,12 @@ class UserService:
|
||||
**fields: Поля для обновления
|
||||
|
||||
Returns:
|
||||
User: Обновленный пользователь или None
|
||||
User: Обновленный пользователь
|
||||
|
||||
Raises:
|
||||
NotFoundError: Если пользователь не найден
|
||||
"""
|
||||
user = cls.get_user_by_id(user_id)
|
||||
if not user:
|
||||
return None
|
||||
|
||||
for field, value in fields.items():
|
||||
setattr(user, field, value)
|
||||
@@ -76,24 +108,21 @@ class UserService:
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def delete_user(cls, user_id: int) -> bool:
|
||||
def delete_user(cls, user_id: int) -> None:
|
||||
"""
|
||||
Удаляет пользователя
|
||||
|
||||
Args:
|
||||
user_id: ID пользователя
|
||||
|
||||
Returns:
|
||||
bool: True если успешно удален
|
||||
Raises:
|
||||
NotFoundError: Если пользователь не найден
|
||||
"""
|
||||
user = cls.get_user_by_id(user_id)
|
||||
if user:
|
||||
user.delete()
|
||||
return True
|
||||
return False
|
||||
user.delete()
|
||||
|
||||
@classmethod
|
||||
def get_tokens_for_user(cls, user: User) -> Dict[str, str]:
|
||||
def get_tokens_for_user(cls, user: User) -> dict[str, str]:
|
||||
"""
|
||||
Генерирует JWT токены для пользователя
|
||||
|
||||
@@ -110,7 +139,7 @@ class UserService:
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def verify_email(cls, user_id: int) -> bool:
|
||||
def verify_email(cls, user_id: int) -> User:
|
||||
"""
|
||||
Подтверждает email пользователя
|
||||
|
||||
@@ -118,29 +147,45 @@ class UserService:
|
||||
user_id: ID пользователя
|
||||
|
||||
Returns:
|
||||
bool: True если успешно подтвержден
|
||||
User: Обновленный пользователь
|
||||
|
||||
Raises:
|
||||
NotFoundError: Если пользователь не найден
|
||||
"""
|
||||
user = cls.get_user_by_id(user_id)
|
||||
if user:
|
||||
user.is_verified = True
|
||||
user.save()
|
||||
return True
|
||||
return False
|
||||
user.is_verified = True
|
||||
user.save()
|
||||
return user
|
||||
|
||||
|
||||
class ProfileService:
|
||||
"""Сервисный слой для работы с профилями"""
|
||||
|
||||
@classmethod
|
||||
def get_profile_by_user_id(cls, user_id: int) -> Optional[Profile]:
|
||||
"""Получает профиль по ID пользователя"""
|
||||
def get_profile_by_user_id(cls, user_id: int) -> Profile:
|
||||
"""Получает профиль по ID пользователя
|
||||
|
||||
Raises:
|
||||
NotFoundError: Если профиль не найден
|
||||
"""
|
||||
try:
|
||||
return Profile.objects.select_related("user").get(user_id=user_id)
|
||||
except Profile.DoesNotExist as e:
|
||||
raise NotFoundError(
|
||||
message=f"Profile for user_id={user_id} not found",
|
||||
details={"user_id": user_id},
|
||||
) from e
|
||||
|
||||
@classmethod
|
||||
def get_profile_by_user_id_or_none(cls, user_id: int) -> Profile | None:
|
||||
"""Получает профиль по ID пользователя или None"""
|
||||
try:
|
||||
return Profile.objects.select_related("user").get(user_id=user_id)
|
||||
except Profile.DoesNotExist:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def update_profile(cls, user_id: int, **fields) -> Optional[Profile]:
|
||||
def update_profile(cls, user_id: int, **fields) -> Profile:
|
||||
"""
|
||||
Обновляет профиль пользователя
|
||||
|
||||
@@ -149,11 +194,12 @@ class ProfileService:
|
||||
**fields: Поля для обновления
|
||||
|
||||
Returns:
|
||||
Profile: Обновленный профиль или None
|
||||
Profile: Обновленный профиль
|
||||
|
||||
Raises:
|
||||
NotFoundError: Если профиль не найден
|
||||
"""
|
||||
profile = cls.get_profile_by_user_id(user_id)
|
||||
if not profile:
|
||||
return None
|
||||
|
||||
for field, value in fields.items():
|
||||
setattr(profile, field, value)
|
||||
@@ -162,7 +208,7 @@ class ProfileService:
|
||||
return profile
|
||||
|
||||
@classmethod
|
||||
def get_full_profile_data(cls, user_id: int) -> Optional[Dict[str, Any]]:
|
||||
def get_full_profile_data(cls, user_id: int) -> dict[str, Any]:
|
||||
"""
|
||||
Получает полные данные пользователя и профиля
|
||||
|
||||
@@ -170,12 +216,12 @@ class ProfileService:
|
||||
user_id: ID пользователя
|
||||
|
||||
Returns:
|
||||
Dict: Полные данные или None
|
||||
Dict: Полные данные
|
||||
|
||||
Raises:
|
||||
NotFoundError: Если профиль не найден
|
||||
"""
|
||||
profile = cls.get_profile_by_user_id(user_id)
|
||||
if not profile:
|
||||
return None
|
||||
|
||||
user = profile.user
|
||||
return {
|
||||
"id": user.id,
|
||||
|
||||
Reference in New Issue
Block a user