This commit is contained in:
Игорь Надеин 2026-01-05 10:59:07 +03:00
parent 14c8f95dd1
commit 6cebd772f2
5 changed files with 904 additions and 5 deletions

View File

@ -2,10 +2,10 @@
<html lang="ru">
<body style="font-family:Arial,Helvetica,sans-serif; line-height:1.5; color:#111;">
<h2>Сброс пароля</h2>
<p>Здравствуйте{{ user.get_full_name|default_if_none:"" }}!</p>
<p>Здравствуйте {{ user.get_full_name|default_if_none:"" }}!</p>
<p>Вы запросили сброс пароля. Нажмите на кнопку ниже для смены пароля:</p>
<div style="padding:20px 0;">
<a href="{{ protocol }}://{{ domain }}{% url 'auth_front:password_reset_confirm' uidb64=uid token=token %}"
<a href="{{ protocol }}://{{ domain }}{% url 'auth_front:password_reset_confirm' uidb64=uid token=token %}"
style="display:inline-block; padding:12px 24px; background-color:#0d6efd; color:#ffffff; text-decoration:none; border-radius:6px; font-weight:600;">
Сбросить пароль
</a>

View File

@ -0,0 +1,319 @@
# 🔍 Подробный отчет: Проблемы с тестами аутентификации
**Дата:** 5 января 2026
**Файл:** `tests/api/auth/test_auth_and_registration.py`
## 📊 Статистика
```
✅ 8 тестов ПРОШЛИ
❌ 11 тестов ПРОВАЛЕНЫ
━━━━━━━━━━━━━━━━━━━━━
📈 Успешность: 42%
```
## 🔴 ОСНОВНАЯ ПРОБЛЕМА
### 🚨 КРИТИЧЕСКОЕ НЕСООТВЕТСТВИЕ: Модель vs Сериализатор
#### ✅ Модель Employee (models.py:162-165):
```python
gender = models.PositiveSmallIntegerField(
"Пол", choices=GENDER_CHOICES,
default=0, # ✅ Есть дефолт
blank=True # ✅ НЕОБЯЗАТЕЛЬНОЕ
)
avatar = models.ImageField(
"Фото", upload_to="users/avatars",
blank=True, # ✅ НЕОБЯЗАТЕЛЬНОЕ
null=True # ✅ НЕОБЯЗАТЕЛЬНОЕ
)
```
#### ❌ RegisterSerializer (serializers.py:29-67):
```python
class RegisterSerializer(serializers.Serializer):
# ... другие поля ...
avatar = Base64ImageField(required=True) # ❌ ОБЯЗАТЕЛЬНОЕ (противоречие!)
gender = serializers.ChoiceField(
required=True, # ❌ ОБЯЗАТЕЛЬНОЕ (противоречие!)
choices=((1, "Мужской"), (2, "Женский")),
error_messages={
'required': 'Поле "Пол" обязательно для заполнения.',
}
)
```
**⚠️ Проблема:** Сериализатор требует поля, которые в модели **необязательны**. Это создает искусственные требования для API регистрации, не соответствующие структуре БД.
---
### Обязательные поля в RegisterSerializer
**Файл:** `backend/api/v1/employees/serializers.py` (строки 29-67)
```python
class RegisterSerializer(serializers.Serializer):
# ... другие поля ...
avatar = Base64ImageField(required=True) # ❌ ОБЯЗАТЕЛЬНОЕ
gender = serializers.ChoiceField(
required=True, # ❌ ОБЯЗАТЕЛЬНОЕ
choices=((1, "Мужской"), (2, "Женский")),
error_messages={
'required': 'Поле "Пол" обязательно для заполнения.',
}
)
```
### Что отправляют тесты
**Файл:** `tests/api/auth/test_auth_and_registration.py` (строка 44)
```python
def register_payload(**overrides):
data = {
"first_name": "Иван",
"last_name": "Иванов",
"phone_number": "+79990000001",
"email": "ivan@example.com",
"password": "Str0ngPass!",
"birth_date": "1990-01-01",
"telegram": "@ivan",
# ❌ НЕТ avatar
# ❌ НЕТ gender
}
return data
```
## 📝 Ошибки валидации
Все 11 проваленных тестов получают одинаковую ошибку:
```python
[REGISTER] Validation errors: {
'avatar': [ErrorDetail(string='Ни одного файла не было отправлено.', code='required')],
'gender': [ErrorDetail(string='Поле "Пол" обязательно для заполнения.', code='required')]
}
```
**HTTP статус:** `400 Bad Request` (ожидается `201 Created`)
## 📋 Детальный список проваленных тестов
### 1. ❌ test_register_success_sends_email_and_user_inactive
**Цель:** Проверка успешной регистрации с отправкой email
**Проблема:** Не передает `avatar` и `gender`
### 2. ❌ test_register_duplicate_email_phone
**Цель:** Проверка дубликатов email/phone
**Проблема:** Не может создать первого пользователя (нет `avatar`, `gender`)
### 3. ❌ test_register_accepts_optional_fields
**Цель:** Проверка опциональных полей (patronymic, whatsapp)
**Проблема:** Передает `gender=1`, но **не передает `avatar`**
```python
# Частичное исправление в тесте:
payload = {
...
"patronymic": "Петрович",
"gender": 1, # ✅ Есть
"whatsapp": "+79995550011",
# ❌ Нет avatar
}
```
### 4-11. ❌ Тесты верификации email и логина
Все зависят от `register()` функции, которая не передает обязательные поля:
- `test_verify_email_success_activates_user`
- `test_verify_email_wrong_code`
- `test_resend_email_sends_new_code_and_old_becomes_invalid`
- `test_verify_expired_more_than_5_minutes_deletes_account`
- `test_login_by_email_denied_before_verify`
- `test_login_by_email_allowed_after_verify`
- `test_login_by_phone_allowed_after_verify`
- `test_login_wrong_password`
## ✅ Успешные тесты (8 штук)
Эти тесты проверяют **валидацию отсутствующих полей**, поэтому они ожидают `400 Bad Request`:
1. ✅ `test_register_missing_required_field[first_name]`
2. ✅ `test_register_missing_required_field[last_name]`
3. ✅ `test_register_missing_required_field[phone_number]`
4. ✅ `test_register_missing_required_field[email]`
5. ✅ `test_register_missing_required_field[password]`
6. ✅ `test_register_missing_required_field[birth_date]`
7. ✅ `test_register_requires_at_least_one_contact`
8. ✅ `test_register_birth_date_must_be_valid_date`
**Почему они проходят?** Они ожидают ошибку валидации и получают её (хоть и по другим причинам).
## 🔍 Анализ причин
### Вопрос 1: Почему `avatar` и `gender` обязательные?
Вероятно, это бизнес-требование:
- **Avatar:** Для корпоративной системы важно иметь фото сотрудника
- **Gender:** Для HR-целей и документооборота
### Вопрос 2: Должны ли они быть обязательными при регистрации?
**Два подхода:**
#### Вариант A: Сделать опциональными для регистрации
```python
avatar = Base64ImageField(required=False, allow_null=True)
gender = serializers.ChoiceField(
required=False,
allow_null=True,
choices=((1, "Мужской"), (2, "Женской")),
)
```
**Плюсы:** Упрощает регистрацию
**Минусы:** Может нарушить бизнес-логику
#### Вариант B: Исправить тесты (добавить поля)
```python
def register_payload(**overrides):
data = {
"first_name": "Иван",
"last_name": "Иванов",
"phone_number": "+79990000001",
"email": "ivan@example.com",
"password": "Str0ngPass!",
"birth_date": "1990-01-01",
"telegram": "@ivan",
"avatar": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", # 1x1 прозрачный PNG
"gender": 1, # Мужской
}
return data
```
**Плюсы:** Соответствует текущей бизнес-логике
**Минусы:** Нужно обновить все тесты
## 🎯 Рекомендации
### ✅ РЕКОМЕНДУЕМОЕ РЕШЕНИЕ: Исправить сериализатор
**Причина:** Модель Employee позволяет `avatar` и `gender` быть необязательными. Сериализатор должен соответствовать модели.
**Изменение в `backend/api/v1/employees/serializers.py`:**
```python
class RegisterSerializer(serializers.Serializer):
# ... другие поля ...
# Было:
# avatar = Base64ImageField(required=True)
# gender = serializers.ChoiceField(required=True, ...)
# Стало:
avatar = Base64ImageField(required=False, allow_null=True) # ✅ Соответствует модели
gender = serializers.ChoiceField(
required=False, # ✅ Соответствует модели
choices=((1, "Мужской"), (2, "Женский"), (0, "Не указано")),
default=0, # ✅ Соответствует модели
error_messages={
'invalid_choice': 'Выберите корректное значение пола.',
}
)
```
**Преимущества:**
- ✅ Соответствие архитектуре (модель → сериализатор → API)
- ✅ Упрощение регистрации для пользователей
- ✅ Тесты пройдут без изменений
- ✅ Возможность заполнить профиль позже
---
### Альтернатива: Обновить тесты (НЕ рекомендуется)
**Обновить `register_payload()` в тестах:**
```python
def register_payload(**overrides):
# Минимальное base64 изображение (1x1 пиксель)
MINIMAL_AVATAR = (
"data:image/png;base64,"
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=="
)
data = {
"first_name": "Иван",
"last_name": "Иванов",
"phone_number": "+79990000001",
"email": "ivan@example.com",
"password": "Str0ngPass!",
"birth_date": "1990-01-01",
"telegram": "@ivan",
"avatar": MINIMAL_AVATAR, # ✅ Добавлено
"gender": 1, # ✅ Добавлено (1 = Мужской)
}
data.update(overrides)
return data
```
**Недостатки этого подхода:**
- ❌ Не решает архитектурную проблему (несоответствие модели и сериализатора)
- ❌ Усложняет регистрацию в production (требует обязательную загрузку аватара)
- ❌ Требует изменений в тестах вместо исправления кода
---
### Долгосрочное решение (архитектурное)
**Вариант 1:** Разделить регистрацию на этапы
1. **Этап 1:** Email, пароль, базовая информация
2. **Этап 2:** После верификации email → заполнение профиля (avatar, gender)
**Вариант 2:** Сделать поля условно обязательными
- При регистрации: опциональные
- При активации аккаунта: требуется заполнить
- При первом входе: напоминание заполнить
## 📊 Влияние на production
### ⚠️ Текущая ситуация
Если `avatar` и `gender` обязательны:
- Фронтенд **должен** отправлять эти поля при регистрации
- Если фронтенд не отправляет → регистрация не работает
- Пользователи не могут зарегистрироваться
### ✅ Вопросы для проверки
1. **Работает ли регистрация на production?**
- Если да → значит фронтенд корректно отправляет поля
- Если нет → критическая ошибка
2. **Как фронтенд отправляет avatar?**
- Base64 строка
- File upload
- URL изображения
3. **Обязателен ли gender в бизнес-логике?**
- Требование HR
- Требование законодательства
- Опционально
## 🚀 Заключение
**Основная проблема:** Рассинхронизация между тестами и сериализатором
- **Сериализатор** требует `avatar` и `gender`
- **Тесты** не передают эти поля
**Решение:** Нужно привести в соответствие:
- ЛИБО сделать поля опциональными в сериализаторе
- ЛИБО добавить поля в тестовые данные
**Рекомендация:** Обновить тесты (добавить `avatar` и `gender`), так как это, вероятно, соответствует реальному поведению фронтенда.

View File

@ -0,0 +1,374 @@
# 🔍 Анализ: Восстановление пароля не отправляет email
**Дата:** 5 января 2026
**Статус:** ⚠️ Email не отправляется при восстановлении пароля
**Тип:** Логика фильтрации пользователей в Django PasswordResetForm
## 📋 Проблема
Пользователи жалуются, что при нажатии кнопки "Забыли пароль?" не приходит письмо на почту.
### Логи системы:
```
[INFO] 08:52:00 HTTP GET /auth/password-reset/ 200 ← Открыта форма
[INFO] 08:52:24 HTTP POST /auth/password-reset/ 302 ← Форма отправлена
[INFO] 08:52:24 HTTP GET /auth/password-reset/done/ 200 ← Показана страница "готово"
```
**Проблема:** Нет записей об отправке email! Ожидалось что-то вроде:
```
[INFO] Email отправлен: password_reset → user@example.com
```
## 🔍 Исследование
### Код восстановления пароля
**Используется:** Django встроенный `PasswordResetView` и `PasswordResetForm`
[employees/views_auth.py](backend/employees/views_auth.py#L217-L222):
```python
class PasswordResetView(DjangoPasswordResetView):
template_name = "auth/password_reset.html"
email_template_name = "auth/password_reset_email.txt"
subject_template_name = "auth/password_reset_subject.txt"
success_url = "/auth/password-reset/done/"
```
### Django PasswordResetForm.get_users()
Проверил исходный код Django `PasswordResetForm`:
```python
def get_users(self, email):
"""Given an email, return matching user(s) who should receive a reset.
This allows subclasses to more easily customize the default policies
that prevent inactive users and users with unusable passwords from
resetting their password.
"""
email_field_name = UserModel.get_email_field_name()
active_users = UserModel._default_manager.filter(
**{
"%s__iexact" % email_field_name: email,
"is_active": True, # ← 🚨 ФИЛЬТР НА АКТИВНЫХ!
}
)
return (
u
for u in active_users
if u.has_usable_password()
and _unicode_ci_compare(email, getattr(u, email_field_name))
)
```
**Ключевая строка:** `"is_active": True`
### Логика регистрации в EUSRR
[api/v1/employees/views.py](backend/api/v1/employees/views.py#L473-L477):
```python
# Режим без LDAP: создаём пользователя напрямую в БД
emp = Employee.objects.create(
first_name=v["first_name"],
last_name=v["last_name"],
email=email,
phone_number=phone_norm,
is_active=False, # ← не активен до верификации email
is_ldap_managed=False,
)
```
Пользователь становится активным только после верификации email:
[api/v1/employees/views.py](backend/api/v1/employees/views.py#L1778-L1788):
```python
# Верификация email
emp = Employee.objects.filter(email__iexact=email).first()
# ... проверка кода ...
# Активируем пользователя
emp.is_active = True # ← Активируем
emp.email_verified = True
emp.save(update_fields=["is_active", "email_verified", "email_activation_code"])
```
## 🎯 КОРНЕВАЯ ПРИЧИНА
### Сценарий 1: Незаверифицированный пользователь
1. **Пользователь регистрируется** → `is_active=False`, `email_verified=False`
2. **НЕ верифицирует email** (не ввел код из письма)
3. **Пытается восстановить пароль**
4. Django `PasswordResetForm.get_users()` фильтрует: `is_active=True`
5. **Пользователь не найден** → email НЕ отправляется
6. Но форма показывает "done" (чтобы не раскрывать существование аккаунта)
### Сценарий 2: Верифицированный пользователь
1. **Пользователь регистрируется** → `is_active=False`, `email_verified=False`
2. **Верифицирует email** → `is_active=True`, `email_verified=True`
3. **Пытается восстановить пароль**
4. Django `PasswordResetForm.get_users()` находит пользователя
5. **Email отправляется** ✅
## 📊 Диагностика
### Проверка текущего статуса пользователей:
```bash
.venv/Scripts/python manage.py shell
```
```python
from employees.models import Employee
# Незаверифицированные пользователи
unverified = Employee.objects.filter(
is_active=False,
email_verified=False
).count()
print(f"Незаверифицированных: {unverified}")
# Заверифицированные пользователи
verified = Employee.objects.filter(
is_active=True,
email_verified=True
).count()
print(f"Заверифицированных: {verified}")
# Проблемные: email verified, но is_active=False (не должны существовать)
problem = Employee.objects.filter(
is_active=False,
email_verified=True
).count()
print(f"Проблемных: {problem}")
```
### Тест восстановления пароля:
```bash
.venv/Scripts/python manage.py shell
```
```python
from django.contrib.auth.forms import PasswordResetForm
from employees.models import Employee
# Создать тестового неактивного пользователя
test_user = Employee.objects.create(
email="test@example.com",
first_name="Test",
last_name="User",
phone_number="+79990001234",
is_active=False, # НЕ активен
telegram="@test"
)
test_user.set_password("password123")
test_user.save()
# Попытка сброса пароля
form = PasswordResetForm(data={"email": "test@example.com"})
if form.is_valid():
users = list(form.get_users(form.cleaned_data["email"]))
print(f"Найдено пользователей: {len(users)}") # ← Должно быть 0!
else:
print("Форма невалидна")
# Очистка
test_user.delete()
```
**Ожидаемый результат:** `Найдено пользователей: 0` для неактивного пользователя.
## ✅ РЕШЕНИЯ
### Вариант 1: Переопределить get_users() (рекомендуется)
Создать кастомную форму, которая позволяет сбрасывать пароль незаверифицированным пользователям:
**Создать:** `backend/employees/forms_auth.py` (дополнение)
```python
from django.contrib.auth.forms import PasswordResetForm as DjangoPasswordResetForm
from django.contrib.auth import get_user_model
class CustomPasswordResetForm(DjangoPasswordResetForm):
"""Форма сброса пароля, разрешающая сброс для неактивных пользователей"""
def get_users(self, email):
"""Находит пользователей по email, включая неактивных.
Отличие от Django: не фильтрует по is_active=True,
чтобы пользователи могли восстановить пароль до верификации email.
"""
UserModel = get_user_model()
email_field_name = UserModel.get_email_field_name()
# Убираем фильтр is_active=True
users = UserModel._default_manager.filter(
**{f"{email_field_name}__iexact": email}
)
return (
u for u in users
if u.has_usable_password()
and u.email.lower() == email.lower()
)
```
**Обновить:** `backend/employees/views_auth.py`
```python
from .forms_auth import CustomPasswordResetForm
class PasswordResetView(DjangoPasswordResetView):
template_name = "auth/password_reset.html"
email_template_name = "auth/password_reset_email.txt"
subject_template_name = "auth/password_reset_subject.txt"
success_url = "/auth/password-reset/done/"
form_class = CustomPasswordResetForm # ← Использовать кастомную форму
```
**Преимущества:**
- ✅ Пользователи могут восстановить пароль до верификации email
- ✅ Стандартное поведение Django, просто без фильтра `is_active`
- ✅ Безопасно (форма все равно не раскрывает существование аккаунта)
**Недостатки:**
- ⚠️ Пользователи без верификации email могут получить доступ
### Вариант 2: Активировать при регистрации
Изменить логику: `is_active=True` сразу, но блокировать вход через middleware до верификации.
**Изменить:** `backend/api/v1/employees/views.py`
```python
emp = Employee.objects.create(
# ...
is_active=True, # ← Активен сразу
email_verified=False, # ← Но email не подтвержден
is_ldap_managed=False,
)
```
**Создать middleware:** `backend/eusrr_backend/middleware.py` (дополнение)
```python
class EmailVerificationRequiredMiddleware:
"""Блокирует доступ пользователям без верификации email"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.user.is_authenticated:
if not request.user.email_verified:
# Разрешить только страницы верификации
allowed = [
'/auth/verify-email/',
'/auth/resend-email/',
'/auth/logout/',
]
if not any(request.path.startswith(p) for p in allowed):
return redirect('auth_front:verify_email')
return self.get_response(request)
```
**Преимущества:**
- ✅ Восстановление пароля работает для всех
- ✅ Безопасность через middleware
**Недостатки:**
- ⚠️ Более сложная логика
- ⚠️ Требует тщательного тестирования
### Вариант 3: Разрешить вход без верификации (НЕ рекомендуется)
Просто убрать требование верификации email вообще.
**Недостатки:**
- ❌ Небезопасно (фейковые email)
- ❌ Не соответствует логике системы
## 🧪 Тестирование после фикса
### 1. Тест незаверифицированного пользователя:
```python
# 1. Создать пользователя (is_active=False)
# 2. Попытаться восстановить пароль
# 3. Проверить что email отправлен
# 4. Перейти по ссылке из письма
# 5. Установить новый пароль
# 6. Войти с новым паролем
```
### 2. Тест заверифицированного пользователя:
```python
# 1. Создать пользователя и верифицировать email
# 2. Попытаться восстановить пароль
# 3. Проверить что email отправлен
# 4. Установить новый пароль
# 5. Войти
```
## 📈 Дополнительные проверки
### Настройки EMAIL
Проверить [settings.py](backend/eusrr_backend/settings.py#L233-L246):
```python
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.yandex.ru'
EMAIL_PORT = 465
EMAIL_HOST_USER = 'robotail-info@yandex.ru'
EMAIL_HOST_PASSWORD = '***' # Проверить что пароль валиден
EMAIL_USE_SSL = True
```
**Тест отправки email:**
```bash
.venv/Scripts/python manage.py shell
```
```python
from django.core.mail import send_mail
send_mail(
'Test Subject',
'Test Message',
'robotail-info@yandex.ru',
['recipient@example.com'],
fail_silently=False,
)
# Если ошибка → проверить настройки SMTP
```
### Шаблоны писем
Проверить существование:
- ✅ `backend/templates/auth/password_reset_email.txt`
- ✅ `backend/templates/auth/password_reset_subject.txt`
## 🎯 Рекомендация
**Использовать Вариант 1** - переопределить `get_users()`:
1. Минимальные изменения кода
2. Решает проблему напрямую
3. Безопасно (не раскрывает существование аккаунтов)
4. Пользователи смогут восстановить пароль в любом статусе
**Дополнительно:** Добавить в письмо восстановления пароля ссылку на верификацию email, если он не подтвержден.
---
**Приоритет:** 🔴 ВЫСОКИЙ - пользователи не могут восстановить доступ
**Трудозатраты:** ⚡ 10-15 минут на реализацию Варианта 1

View File

@ -0,0 +1,206 @@
# 🔴 КРИТИЧЕСКАЯ ОШИБКА: Регистрация падает с IntegrityError
**Дата:** 5 января 2026
**Статус:** 🚨 БЛОКИРУЕТ регистрацию пользователей
**Тип:** Синхронизация БД и модели - несоответствие
## 📋 Проблема
```
IntegrityError at /api/v1/auth/register/
NOT NULL constraint failed: employees_employee.synology_username
```
### Что происходит?
1. **Пользователь отправляет данные на** `/api/v1/auth/register/`
2. **Django пытается создать Employee** с помощью `Employee.objects.create()`
3. **SQLite попадает на колонки в БД**, которых НЕТ в модели Django
4. **Результат:** пустые значения для этих колонок
5. **БД отвергает:** поле `synology_username` помечено как `NOT NULL` → ошибка
## 🔍 Диагностика
### В БД (SQLite) существуют колонки:
```sql
PRAGMA table_info(employees_employee);
-- Результат grep:
(25, 'synology_user_id', 'INTEGER', 0, None, 0)
(26, 'synology_username', 'varchar(100)', 1, None, 0) -- ❌ NOT NULL!
(27, 'synology_session_expires', 'datetime', 0, None, 0)
(28, 'synology_session_id', 'varchar(255)', 1, None, 0) -- ❌ NOT NULL!
```
**Пояснение колонок (слева направо):**
- `0` = порядковый номер
- `'synology_username'` = название колонки
- `'varchar(100)'` = тип данных
- `1` = NOT NULL constraint (1 = есть constraint, 0 = нет)
- `None` = default значение
- `0` = PRIMARY KEY flag
### В модели (employees/models.py):
```python
class Employee(AbstractUser):
# ... другие поля ...
# ❌ SYNOLOGY ПОЛЕЙ НЕТ!
# synology_user_id - не определено
# synology_username - не определено
# synology_session_expires - не определено
# synology_session_id - не определено
```
## 🚨 КОРНЕВАЯ ПРИЧИНА
### Сценарий что произошло:
1. **На ранней стадии разработки** были добавлены Synology поля в БД (миграция была создана)
2. **Позже эти поля были удалены из модели** Employee (вероятно, закомментированы или удалены)
3. **НО соответствующая миграция удаления никогда не была создана** (`RemoveField`)
4. **Результат:** Мисматч между:
- ✅ Тем, что Django ожидает создать (модель)
- ⚠️ Тем, что реально в БД (таблица с Synology полями)
### Почему ошибка происходит именно при регистрации?
Когда Django создаёт объект:
```python
emp = Employee.objects.create(
first_name=v["first_name"],
last_name=v["last_name"],
email=email,
phone_number=phone_norm,
is_active=False,
is_ldap_managed=False,
# ❌ synology_username НЕ УКАЗАН (его нет в модели)
# ❌ synology_session_id НЕ УКАЗАН (его нет в модели)
)
```
SQLite вставляет `NULL` для недостающих полей, но:
- `synology_username` имеет `NOT NULL` constraint → **ОШИБКА**
- `synology_session_id` имеет `NOT NULL` constraint → **ОШИБКА**
## ✅ РЕШЕНИЕ
### Вариант 1: Удалить поля из БД (рекомендуется)
Создать миграцию удаления:
```bash
.venv/Scripts/python manage.py makemigrations employees --empty --name remove_synology_fields
```
Затем заполнить миграцию:
```python
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('employees', 'PREV_MIGRATION'), # последняя миграция
]
operations = [
migrations.RemoveField(
model_name='employee',
name='synology_user_id',
),
migrations.RemoveField(
model_name='employee',
name='synology_username',
),
migrations.RemoveField(
model_name='employee',
name='synology_session_expires',
),
migrations.RemoveField(
model_name='employee',
name='synology_session_id',
),
]
```
Затем применить:
```bash
.venv/Scripts/python manage.py migrate
```
### Вариант 2: Вернуть поля в модель
Если Synology интеграция ещё нужна, вернуть поля в модель:
```python
class Employee(AbstractUser):
# ... другие поля ...
synology_user_id = models.IntegerField(null=True, blank=True)
synology_username = models.CharField(
max_length=100,
blank=True,
null=True, # ← Чтобы Django мог вставлять NULL
verbose_name="Synology Username"
)
synology_session_expires = models.DateTimeField(null=True, blank=True)
synology_session_id = models.CharField(
max_length=255,
blank=True,
null=True, # ← Чтобы Django мог вставлять NULL
verbose_name="Synology Session ID"
)
```
Затем создать миграцию:
```bash
.venv/Scripts/python manage.py makemigrations
```
**Внимание:** Миграция обнаружит уже существующие поля в БД, может показать `AlterField` вместо `AddField`.
### Вариант 3: Очистить и пересоздать БД (только для разработки)
```bash
rm backend/db.sqlite3
.venv/Scripts/python manage.py migrate
```
## 📊 Рекомендация
**Используйте Вариант 1** - удаление полей:
1. Synology поля очевидно не используются (они отсутствуют в коде)
2. Это чистит БД и приводит её в соответствие с моделью
3. Регистрация сразу же начнёт работать
4. Если Synology интеграция понадобится позже, её легко добавить заново
## 🔗 Связанные проблемы
Эта ошибка влияет на:
- ❌ **Регистрация пользователей** (новые пользователи не могут зарегистрироваться)
- ❌ **API endpoint** `/api/v1/auth/register/`
- ❌ **Фронт-end регистрация** (через views_auth.py)
- ✅ Всё остальное (логин, профиль) работает нормально
## 🧪 Тестирование после фикса
После удаления полей:
1. Попробовать регистрацию через API
2. Проверить, что пользователь создан в БД
3. Выполнить `python manage.py check` для валидации схемы
4. Запустить тесты регистрации
```bash
pytest tests/api/auth/test_auth_and_registration.py::test_register_success_sends_email_and_user_inactive -v
```
---
**Приоритет:** 🔴 КРИТИЧЕСКИЙ - блокирует основной функционал
**Трудозатраты:** ⚡ 5-10 минут на реализацию + 2-3 минуты на тестирование

View File

@ -1,6 +1,6 @@
# 🎉 Отчет о рефакторинге тестов
зне# 🎉 Отчет о рефакторинге тестов
**Дата:** 5 января 2026
**Дата:** 5 января 2026
**Статус:** ЗАВЕРШЕНО ✅
## 📊 Результаты рефакторинга
@ -125,5 +125,5 @@ pytest -m integration
- ✅ Manual tests правильно изолированы
- ✅ Конфигурация оптимизирована
**Наше исправление (коммит `de0caea`):** БЕЗОПАСНО ✅
**Наше исправление (коммит `de0caea`):** БЕЗОПАСНО ✅
**Готовность к production:** ДА 🚀