удалил лишнего

This commit is contained in:
lizerk 2026-03-09 17:28:08 +03:00
parent 5a6dc011dd
commit a14a58cace
6 changed files with 0 additions and 1439 deletions

View File

@ -1,207 +0,0 @@
# 🔍 Команды для отладки календаря через консоль браузера
Откройте консоль браузера (F12 → Console) и выполните эти команды:
## 1️⃣ Проверить наличие интеграции
```javascript
// Должно вернуть объект, а не undefined
window.calendarIntegration
```
**Ожидаемый результат:**
```javascript
{
fetchEventsForVisibleCalendars: ƒ,
getVisibleCalendarIds: ƒ,
getCalendars: ƒ,
setVisibleCalendars: ƒ,
refresh: ƒ,
instances: {manager: {...}, modal: {...}, widget: {...}}
}
```
**Если undefined** → интеграция не инициализировалась!
---
## 2️⃣ Проверить виджет календаря
```javascript
window.calendarWidget
```
**Должен вернуть объект FullCalendar**
---
## 3️⃣ Проверить список календарей
```javascript
// Если интеграция работает:
window.calendarIntegration.getCalendars()
// Должно вернуть массив календарей:
// [{id: 1, title: "Personal", color: "#0d6efd", ...}, ...]
```
---
## 4️⃣ Проверить видимые календари
```javascript
window.calendarIntegration.getVisibleCalendarIds()
// Должно вернуть массив ID: [1, 2, 3]
```
---
## 5️⃣ Вручную загрузить события
```javascript
// Попробовать загрузить события для видимых календарей
const start = new Date('2026-02-01');
const end = new Date('2026-02-28');
window.calendarIntegration.fetchEventsForVisibleCalendars(start, end)
.then(events => {
console.log('✅ Events loaded:', events.length);
console.table(events);
})
.catch(err => {
console.error('❌ Error:', err);
});
```
---
## 6️⃣ Проверить, загружен ли скрипт интеграции
```javascript
// Проверить, есть ли в DOM скрипт
document.querySelector('script[src*="calendarWidgetIntegration"]')
// Должен вернуть <script> элемент, если файл подключен
```
---
## 7️⃣ Вручную инициализировать интеграцию
Если интеграция не загрузилась автоматически, попробуйте вручную:
```javascript
// 1. Импортировать модуль (если ES6 modules работают)
import('/static/js/components/calendarWidgetIntegration.js')
.then(module => {
const { integrateCalendarManager } = module;
const integration = integrateCalendarManager(window.calendarWidget);
window.calendarIntegration = integration;
console.log('✅ Integration manually initialized');
})
.catch(err => console.error('❌ Import failed:', err));
```
---
## 8️⃣ Перезагрузить события календаря
```javascript
// После того как интеграция инициализирована
if (window.calendarWidget && window.calendarWidget.refetchEvents) {
window.calendarWidget.refetchEvents();
console.log('🔄 Events refetched');
}
```
---
## 9️⃣ Проверить контейнер списка календарей
```javascript
// Проверить, существует ли контейнер
const container = document.getElementById('calendarListContainer');
console.log('Container exists:', !!container);
console.log('Container HTML:', container?.innerHTML.substring(0, 200));
```
---
## 🔟 Проверить модальное окно
```javascript
// Проверить, есть ли модальное окно
const modal = document.getElementById('calendarManageModal');
console.log('Modal exists:', !!modal);
// Попробовать открыть модальное окно
if (window.calendarIntegration?.instances?.modal) {
window.calendarIntegration.instances.modal.openForCreate();
}
```
---
## 🐛 Диагностика проблемы
Выполните все команды по порядку и скопируйте результаты. Это поможет понять, на каком этапе происходит сбой:
```javascript
console.log('=== Calendar Integration Debug ===');
console.log('1. calendarIntegration:', typeof window.calendarIntegration);
console.log('2. calendarWidget:', typeof window.calendarWidget);
console.log('3. Container:', !!document.getElementById('calendarListContainer'));
console.log('4. Modal:', !!document.getElementById('calendarManageModal'));
console.log('5. Create button:', !!document.querySelector('[data-action="create-calendar"]'));
console.log('=================================');
```
---
## 🔧 Если интеграция undefined
Проверьте, загружается ли файл `calendar_scripts.html`:
```javascript
// Найти все script теги с calendar
Array.from(document.querySelectorAll('script[type="module"]'))
.filter(s => s.textContent.includes('calendar'))
.forEach(s => console.log('Found:', s.textContent.substring(0, 100)));
```
---
## 📊 Полный отчёт о состоянии
Скопируйте и выполните весь блок:
```javascript
(function debugCalendar() {
const report = {
integration: typeof window.calendarIntegration,
widget: typeof window.calendarWidget,
container: !!document.getElementById('calendarListContainer'),
modal: !!document.getElementById('calendarManageModal'),
createButton: !!document.querySelector('[data-action="create-calendar"]'),
scripts: Array.from(document.querySelectorAll('script[type="module"]')).length,
errors: []
};
if (window.calendarIntegration) {
try {
report.calendars = window.calendarIntegration.getCalendars().length;
report.visibleIds = window.calendarIntegration.getVisibleCalendarIds();
} catch (e) {
report.errors.push('Error accessing integration: ' + e.message);
}
}
console.table(report);
return report;
})();
```
---
Выполните команды и покажите результаты! 🔍

View File

@ -1,60 +0,0 @@
# 🔧 Исправление проблемы инициализации calendar integration
## Проблема
`window.calendarIntegration` = undefined, но скрипт подключён.
## Быстрое решение - выполните в консоли:
```javascript
// Проверка наличия скрипта
const scripts = Array.from(document.querySelectorAll('script[type="module"]'));
console.log('Module scripts found:', scripts.length);
scripts.forEach((s, i) => {
if (s.textContent.includes('calendar')) {
console.log(`Script ${i}:`, s.textContent.substring(0, 200));
}
});
```
## Если скрипт найден, но не работает - вручную загрузите:
```javascript
(async function manualInit() {
try {
console.log('🔄 Manually loading calendar integration...');
// Импорт модуля
const module = await import('/static/js/components/calendarWidgetIntegration.js');
console.log('✅ Module loaded:', module);
// Инициализация
const { integrateCalendarManager } = module;
const integration = integrateCalendarManager(window.calendarWidget);
if (integration) {
window.calendarIntegration = integration;
console.log('✅ Integration initialized successfully!');
console.log('Calendars:', integration.getCalendars().length);
console.log('Visible:', integration.getVisibleCalendarIds());
// Перезагрузить события
if (window.calendarWidget.refetchEvents) {
window.calendarWidget.refetchEvents();
console.log('🔄 Events refetched with new integration');
}
}
} catch (error) {
console.error('❌ Manual init failed:', error);
}
})();
```
## После выполнения проверьте:
```javascript
// Должно вернуть объект, а не undefined
window.calendarIntegration
// Должно показать логи с новой системой
window.calendarWidget.refetchEvents()
```

View File

@ -1,761 +0,0 @@
# Тестирование Прав Доступа Через Должности (Position Permissions)
## ✅ РЕШЕНИЕ НАЙДЕНО И ПРИМЕНЕНО
**Дата:** 8 декабря 2025
**Статус:** ✅ Исправлено и протестировано
### Корневая Причина
В шаблоне `feed/feed_list.html` проверялись **несуществующие** права:
```django
{% if user.is_staff or perms.feed.publish_company_post or perms.feed.publish_department_post %}
```
Эти права **никогда не создавались** в Django! Правильное право для создания постов:
```django
{% if user.is_staff or perms.feed.add_post %}
```
### Диагностика Production
Запущен отладочный скрипт для пользователя `maze3290@gmail.com`:
```python
User: maze3290@gmail.com
Position: СММ специалист
Position Groups: ['Дизайн СММ Реклама']
Group Permissions: ['add_post', 'change_post', 'delete_post', 'view_post', ...]
User Permissions (via PositionRoleBackend):
{'feed.add_post', 'feed.change_post', 'feed.delete_post', ...}
Has feed.publish_company_post: False # ❌ Несуществующее право!
Has feed.add_post: True # ✅ Реальное право!
```
**Вывод:** PositionRoleBackend работает правильно, проблема была в шаблоне!
### Исправление
**Файл:** `backend/templates/feed/feed_list.html` (строка 19)
```diff
- {% if user.is_staff or perms.feed.publish_company_post or perms.feed.publish_department_post %}
+ {% if user.is_staff or perms.feed.add_post %}
```
### Коммиты
1. `769f2ed` - test: add comprehensive position permissions tests
2. `66406d9` - fix: add is_active check to PositionRoleBackend
3. `4a6b92f` - docs: add production debugging guide
4. `6162d9f` - **fix: correct permission check in feed_list.html** ← РЕШЕНИЕ
---
## Описание Проблемы (Оригинал)
### Исходная Жалоба Пользователя
> "Ты говорил, что пользователь с должностью получает все права которые есть у групп в этой должности, но по факту кнопка создания публикаций не появляется"
**Ожидаемое поведение:**
- Пользователь имеет должность (Position)
- Должность связана с группами (Position.groups)
- Группы имеют права (Group.permissions)
- Пользователь должен получить все права из групп своей должности
**Фактическое поведение:**
- UI кнопки не появляются (например, "Создать публикацию")
- Пользователи с должностями не могут выполнять действия, несмотря на наличие прав в группах
## Архитектура Системы Прав
### Цепочка Наследования
```
Position → Position.groups (ManyToMany) → Group.permissions (ManyToMany) → Permission
↓
Employee.position (ForeignKey)
↓
User (связан через OneToOne)
```
### Реализация Backend
**Файл:** `backend/eusrr_backend/auth_backends.py`
```python
class PositionRoleBackend(BaseBackend):
"""
Custom authentication backend that grants permissions based on
Position.groups relationships.
"""
def get_all_permissions(self, user_obj, obj=None):
if not user_obj.is_active or user_obj.is_anonymous:
return set()
# Получаем права из должности
if hasattr(user_obj, 'employee') and user_obj.employee.position:
pos = user_obj.employee.position
return set(
Permission.objects.filter(
group__positions=pos
).select_related('content_type').values_list(
'content_type__app_label',
'codename'
)
)
return set()
```
**Конфигурация:** `settings.py` (строка 329)
```python
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'eusrr_backend.auth_backends.LDAPAuthBackend',
'eusrr_backend.auth_backends.LDAPGroupAuthBackend',
'eusrr_backend.auth_backends.PositionRoleBackend', # ← Наш backend
]
```
### Модели
**Файл:** `backend/employees/models.py`
```python
class Position(models.Model):
"""Должность сотрудника"""
name = models.CharField(max_length=100, unique=True)
groups = models.ManyToManyField(
Group,
related_name='positions',
blank=True
)
class Employee(models.Model):
"""Сотрудник"""
user = models.OneToOneField(User, on_delete=models.CASCADE)
position = models.ForeignKey(
Position,
on_delete=models.SET_NULL,
null=True,
blank=True
)
```
## Тестовая Стратегия
### Цель Тестов
Определить, является ли проблема:
1. **Логической ошибкой** в `PositionRoleBackend` → Тесты упадут
2. **Проблемой конфигурации** (кэширование, LDAP sync, deployment) → Тесты пройдут
### Структура Тестов
**Файл:** `backend/tests/employees/test_position_permissions.py`
#### Базовые Тесты (TestPositionPermissions)
```python
class TestPositionPermissions:
"""Основная функциональность"""
✅ test_user_without_position_has_no_permissions
Проверяет: Пользователь без должности = нет прав
❌ test_user_with_position_gets_permissions
Проверяет: Position с Group → User получает права
❌ test_position_change_updates_permissions
Проверяет: Смена должности → права обновляются
❌ test_remove_position_removes_permissions
Проверяет: Удаление должности → права убираются
❌ test_multiple_users_same_position
Проверяет: Несколько user с одной Position → одинаковые права
❌ test_staff_user_keeps_permissions
Проверяет: Staff users сохраняют is_staff права
✅ test_position_group_permissions_are_additive
Проверяет: Несколько групп → права складываются
❌ test_get_all_permissions_includes_position_perms
Проверяет: get_all_permissions() возвращает права должности
```
#### Граничные Случаи (TestPositionPermissionsEdgeCases)
```python
class TestPositionPermissionsEdgeCases:
"""Edge cases и особые ситуации"""
✅ test_position_without_groups_grants_no_permissions
Проверяет: Position без groups = нет прав
❌ test_inactive_user_has_no_permissions
Проверяет: Неактивный user = нет прав (даже с Position)
❌ test_direct_user_permissions_and_position_permissions
Проверяет: Прямые права user + права Position = аддитивны
```
#### Интеграционные Тесты (TestPositionPermissionsIntegration)
```python
class TestPositionPermissionsIntegration:
"""Интеграция с реальными моделями"""
❌ test_feed_publish_permission_via_position
Проверяет: Право "feed.publish_company_post" работает
❌ test_document_add_permission_via_position
Проверяет: Право "documents.add_document" работает
```
### Текущие Результаты
```
============== test session starts ==============
collected 13 items
✅ PASSED: 3 tests
❌ ERRORS: 10 tests
Passing Tests:
- test_user_without_position_has_no_permissions
- test_position_group_permissions_are_additive
- test_position_without_groups_grants_no_permissions
Failing Tests:
- All tests with IntegrityError or fixture issues
```
## Обнаруженные Проблемы в Тестах
### Проблема 1: IntegrityError на Permission Creation
**Ошибка:**
```
IntegrityError: UNIQUE constraint failed: auth_permission.content_type_id, auth_permission.codename
```
**Причина:**
Использование `Permission.objects.get_or_create()` в фикстурах приводит к попыткам создать дубликаты.
**Решение:**
Изменить паттерн создания:
```python
# ❌ Старый код
perm, _ = Permission.objects.get_or_create(
codename='publish_company_post',
content_type=feed_content_type,
defaults={'name': 'Can publish company post'}
)
# ✅ Новый код
perm = Permission.objects.filter(
codename='publish_company_post',
content_type=feed_content_type
).first()
if not perm:
perm = Permission.objects.create(
codename='publish_company_post',
content_type=feed_content_type,
name='Can publish company post'
)
```
**Статус:** Исправлено в 3 местах:
- ✅ Фикстура `group_with_publish_perm` (строки 44-62)
- ✅ Фикстура `group_with_document_perm` (строки 64-82)
- ✅ Тест `test_position_group_permissions_are_additive` (строки 244-269)
### Проблема 2: Fixture Visibility Between Test Classes
**Ошибка:**
```
fixture 'engineer_position' not found
fixture 'feed_content_type' not found
fixture 'documents_content_type' not found
```
**Причина:**
Фикстуры определены в `TestPositionPermissions` как методы класса, но дочерние классы `TestPositionPermissionsEdgeCases` и `TestPositionPermissionsIntegration` не имеют к ним доступа.
**Возможные решения:**
1. Перенести фикстуры на уровень модуля (вне классов)
2. Использовать `@pytest.fixture(scope="module")`
3. Использовать наследование классов
4. Создать фикстуры в `conftest.py`
**Статус:** ❌ Не исправлено
### Проблема 3: Permission Objects Already Exist in Database
**Ошибка:**
При запуске тестов Django уже имеет базовые права из миграций.
**Решение:**
Использовать `.filter().first()` вместо `.create()` для всех Permission объектов.
**Статус:** ✅ Частично исправлено (нужна проверка всех 13 тестов)
## План Исправления
### Шаг 1: Рефакторинг Фикстур ✅ IN PROGRESS
- [x] Заменить `get_or_create` на `filter().first()` + conditional create
- [x] Применить в `group_with_publish_perm`
- [x] Применить в `group_with_document_perm`
- [x] Применить в `test_position_group_permissions_are_additive`
- [ ] Перенести фикстуры на module level
### Шаг 2: Запуск Тестов
```bash
cd /c/Users/Игорь/Dev/EUSRR/backend
pytest tests/employees/test_position_permissions.py -v --tb=short
```
**Ожидаемый результат после исправлений:**
- Все 13 тестов должны запуститься (не ERROR)
- Проверим, сколько PASS vs FAIL
### Шаг 3: Анализ Результатов
#### Сценарий А: Все тесты PASS ✅
**Вывод:** Логика `PositionRoleBackend` корректна
**Действия:**
1. Проверить production конфигурацию:
- `AUTHENTICATION_BACKENDS` order
- Кэширование прав (Django cache)
- LDAP синхронизация групп
2. Проверить данные в БД:
```python
# Django shell
user = User.objects.get(username='...')
print(user.employee.position)
print(user.employee.position.groups.all())
print(user.get_all_permissions()) # Должны быть права из Position
```
3. Проверить middleware и template context
#### Сценарий Б: Некоторые тесты FAIL ❌
**Вывод:** Есть проблема в логике `PositionRoleBackend`
**Действия:**
1. Проанализировать failing assertions
2. Исправить `get_all_permissions()` или `has_perm()`
3. Убедиться что query `Permission.objects.filter(group__positions=pos)` корректен
4. Проверить returns в формате "app_label.codename"
## Проверка UI Visibility
### Текущая Реализация
**Файл:** `backend/templates/feed/feed_list.html` (строка 19)
```html
{% if user.is_staff or perms.feed.publish_company_post or perms.feed.publish_department_post %}
<button class="btn btn-primary">Создать публикацию</button>
{% endif %}
```
**Файл:** `backend/templates/documents/document_list.html` (строки 17-21)
```html
{% if user.is_staff or perms.documents.add_document %}
<a href="{% url 'documents:document_create' %}" class="btn btn-primary">
Добавить документ
</a>
{% endif %}
```
### Логика Проверки в Templates
Django использует `perms.app_label.codename` в шаблонах:
1. Вызывает `user.has_perm('app_label.codename')`
2. Проходит по всем AUTHENTICATION_BACKENDS
3. Если хотя бы один backend вернёт `True` → permission granted
**Требования:**
- `PositionRoleBackend.has_perm()` должен вернуть `True`
- `PositionRoleBackend.get_all_permissions()` должен включать это право
- Формат: tuple `('app_label', 'codename')` или строка `'app_label.codename'`
## Отладочные Команды
### Django Shell Testing
```python
from django.contrib.auth.models import User, Permission, Group
from django.contrib.contenttypes.models import ContentType
from employees.models import Employee, Position
# Получить пользователя
user = User.objects.get(username='test_user')
# Проверить должность
print(f"Position: {user.employee.position}")
print(f"Position Groups: {user.employee.position.groups.all()}")
# Проверить права через backend
from eusrr_backend.auth_backends import PositionRoleBackend
backend = PositionRoleBackend()
all_perms = backend.get_all_permissions(user)
print(f"All Permissions: {all_perms}")
# Проверить has_perm
has_perm = backend.has_perm(user, 'feed.publish_company_post')
print(f"Has feed.publish_company_post: {has_perm}")
# Проверить через Django API
has_perm_django = user.has_perm('feed.publish_company_post')
print(f"User.has_perm('feed.publish_company_post'): {has_perm_django}")
```
### SQL Query Debugging
```python
from django.db import connection
from django.test.utils import CaptureQueriesContext
with CaptureQueriesContext(connection) as queries:
perms = backend.get_all_permissions(user)
for q in queries:
print(q['sql'])
```
### LDAP Group Sync Check
```python
from employees.ldap.services import PositionService
pos = Position.objects.get(name='Engineer')
dn = PositionService._ensure_position_group(pos)
print(f"LDAP Group DN: {dn}")
```
## Ожидаемая SQL Query
При вызове `get_all_permissions()` должен выполниться query:
```sql
SELECT DISTINCT
django_content_type.app_label,
auth_permission.codename
FROM auth_permission
INNER JOIN django_content_type
ON auth_permission.content_type_id = django_content_type.id
INNER JOIN auth_group_permissions
ON auth_permission.id = auth_group_permissions.permission_id
INNER JOIN auth_group
ON auth_group_permissions.group_id = auth_group.id
INNER JOIN employees_position_groups
ON auth_group.id = employees_position_groups.group_id
WHERE employees_position_groups.position_id = ?
```
## Контрольный Чеклист
### Pre-Test Checklist
- [x] Создан файл `tests/employees/test_position_permissions.py`
- [x] 13 тестов покрывают все сценарии
- [x] Фикстуры используют `.filter().first()` для Permission
- [ ] Фикстуры доступны во всех test классах
- [ ] Тесты запускаются без ERROR
### Post-Test Checklist (если тесты проходят)
- [ ] Проверить `user.get_all_permissions()` в production shell
- [ ] Проверить `user.has_perm('feed.publish_company_post')` в production
- [ ] Проверить что `Position.groups` содержит нужные группы
- [ ] Проверить что `Group.permissions` содержит нужные права
- [ ] Проверить порядок AUTHENTICATION_BACKENDS
- [ ] Очистить Django permission cache
- [ ] Перезапустить сервер
### Post-Test Checklist (если тесты не проходят)
- [ ] Проанализировать failing assertions
- [ ] Исправить логику в `PositionRoleBackend.get_all_permissions()`
- [ ] Исправить логику в `PositionRoleBackend.has_perm()`
- [ ] Убедиться что query возвращает правильный формат
- [ ] Проверить related_name в моделях
- [ ] Перезапустить тесты
## Следующие Шаги
1. **Сейчас:** Исправить fixture visibility (перенести на module level)
2. **Потом:** Запустить pytest с verbose output
3. **Далее:** Проанализировать PASS/FAIL результаты
4. **Финал:** Исправить production или backend в зависимости от результатов
## Дополнительная Информация
### Связанные Файлы
- `backend/eusrr_backend/auth_backends.py` - PositionRoleBackend
- `backend/employees/models.py` - Position, Employee
- `backend/templates/feed/feed_list.html` - UI для публикаций
- `backend/templates/documents/document_list.html` - UI для документов
- `backend/tests/employees/test_position_permissions.py` - Тесты
### Связанные Коммиты
- `769f2ed` - test: add comprehensive position permissions tests
### Известные Ограничения
- Права кэшируются Django (может требовать перезапуска)
- LDAP синхронизация может задерживаться
- Staff users всегда имеют все права (is_staff=True)
---
**Документ создан:** 2025-01-XX
**Последнее обновление:** ✅ ВСЕ ТЕСТЫ ПРОШЛИ (13/13 PASSED)
**Статус:** ✅ Готово к production тестированию
## ВАЖНО: Что Делать Дальше
### ✅ Что Мы Выяснили
**Тесты показали, что логика `PositionRoleBackend` работает ПРАВИЛЬНО!**
- ✅ Пользователи с должностями получают права из групп
- ✅ Права обновляются при смене должности
- ✅ Права удаляются при снятии должности
- ✅ `has_perm()` и `get_all_permissions()` работают корректно
- ✅ Добавлена проверка `is_active` (исправили баг)
### 🔍 Где Искать Проблему
Если кнопки **всё ещё не появляются** в production, проблема НЕ в коде, а в:
1. **Кэширование прав Django**
- Django кэширует права в `_perm_cache` и `_user_perm_cache`
- После изменения должности нужен перелогин или очистка кэша
2. **LDAP синхронизация групп**
- Проверить что `Position.groups` содержит правильные группы
- Проверить что `Group.permissions` содержит нужные права
3. **Порядок AUTHENTICATION_BACKENDS**
- `PositionRoleBackend` должен быть в списке (сейчас на 4 месте ✅)
- Возможно другие backend'ы блокируют его работу
### 📋 Чеклист Отладки Production
#### Шаг 1: Проверить Права в Django Shell
```python
from django.contrib.auth import get_user_model
User = get_user_model()
# Замените на реального пользователя
user = User.objects.get(username='имя_пользователя')
print(f"1. Position: {user.position}")
print(f"2. Position Groups: {user.position.groups.all() if user.position else 'NO POSITION'}")
if user.position:
for group in user.position.groups.all():
print(f" - Group: {group.name}")
print(f" Permissions: {list(group.permissions.values_list('codename', flat=True))}")
print(f"\n3. User Permissions:")
print(f" All Permissions: {user.get_all_permissions()}")
print(f" Has feed.publish_company_post: {user.has_perm('feed.publish_company_post')}")
print(f" Has documents.add_document: {user.has_perm('documents.add_document')}")
```
**Ожидаемый результат:**
- `Position` должен быть не `None`
- `Position.groups` должны содержать группы
- `Group.permissions` должны включать нужные права
- `user.get_all_permissions()` должен возвращать права из должности
- `user.has_perm('feed.publish_company_post')` должен быть `True`
#### Шаг 2: Проверить Базу Данных
```sql
-- Проверить связь User → Employee → Position
SELECT u.username, e.id, p.name
FROM auth_user u
JOIN employees_employee e ON e.user_id = u.id
LEFT JOIN employees_position p ON e.position_id = p.id
WHERE u.username = 'имя_пользователя';
-- Проверить связь Position → Groups
SELECT p.name AS position, g.name AS group_name
FROM employees_position p
JOIN employees_position_groups pg ON pg.position_id = p.id
JOIN auth_group g ON pg.group_id = g.id
WHERE p.id = <position_id>;
-- Проверить связь Group → Permissions
SELECT g.name AS group_name, p.codename
FROM auth_group g
JOIN auth_group_permissions gp ON gp.group_id = g.id
JOIN auth_permission p ON gp.permission_id = p.id
WHERE g.id IN (
SELECT group_id FROM employees_position_groups
WHERE position_id = <position_id>
);
```
#### Шаг 3: Очистить Кэш Прав
```python
from django.contrib.auth import get_user_model
User = get_user_model()
user = User.objects.get(username='имя_пользователя')
# Очистить кэши прав
if hasattr(user, '_perm_cache'):
delattr(user, '_perm_cache')
if hasattr(user, '_user_perm_cache'):
delattr(user, '_user_perm_cache')
# Или просто получить fresh instance
user = User.objects.get(id=user.id)
# Проверить снова
print(user.has_perm('feed.publish_company_post'))
```
#### Шаг 4: Проверить Template Context
В шаблоне `feed/feed_list.html`:
```django
{# DEBUG: Покажите права пользователя #}
{% if user.is_authenticated %}
<p>User: {{ user.username }}</p>
<p>Position: {{ user.position }}</p>
<p>Has publish_company_post: {{ perms.feed.publish_company_post }}</p>
<p>Has publish_department_post: {{ perms.feed.publish_department_post }}</p>
<p>Is Staff: {{ user.is_staff }}</p>
{% endif %}
```
#### Шаг 5: Проверить Middleware
В `settings.py` убедитесь что есть:
```python
MIDDLEWARE = [
...
'django.contrib.auth.middleware.AuthenticationMiddleware',
...
]
```
#### Шаг 6: Перезапустить Сервер
```bash
# Остановить
Ctrl+C
# Очистить кэш (если используется Redis/Memcached)
python manage.py shell
>>> from django.core.cache import cache
>>> cache.clear()
>>> exit()
# Запустить снова
python manage.py runserver
```
#### Шаг 7: Перелогинить Пользователя
- Попросите пользователя выйти из системы
- Войти заново
- Проверить видимость кнопок
### 🐛 Возможные Проблемы
#### Проблема 1: Position.groups пустые
**Симптом:** `user.position.groups.all()` возвращает пустой список
**Решение:**
```python
from employees.models import Position
from django.contrib.auth.models import Group
# Найти или создать группу
pub_group = Group.objects.get(name="publishers")
# Назначить группу должности
position = Position.objects.get(name="Инженер")
position.groups.add(pub_group)
```
#### Проблема 2: Group.permissions пустые
**Симптом:** `group.permissions.all()` возвращает пустой список
**Решение:**
```python
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
# Найти Content Type
ct = ContentType.objects.get(app_label="feed", model="post")
# Найти или создать Permission
perm = Permission.objects.filter(
codename="publish_company_post",
content_type=ct
).first()
if not perm:
perm = Permission.objects.create(
codename="publish_company_post",
name="Can publish company post",
content_type=ct
)
# Добавить в группу
pub_group = Group.objects.get(name="publishers")
pub_group.permissions.add(perm)
```
#### Проблема 3: LDAP не синхронизирует группы
**Симптом:** В LDAP нет группы `POS_Инженер`
**Решение:**
```python
from employees.models import Position
from employees.ldap.services import PositionService
position = Position.objects.get(name="Инженер")
dn = PositionService._ensure_position_group(position)
print(f"LDAP Group DN: {dn}")
```
### <20> Итоговая Таблица Результатов Тестов
| Тест | Статус | Что проверяет |
|------|--------|---------------|
| test_user_without_position_has_no_permissions | ✅ PASS | Без должности = нет прав |
| test_user_with_position_gets_permissions | ✅ PASS | С должностью = права есть |
| test_position_change_updates_permissions | ✅ PASS | Смена должности обновляет права |
| test_remove_position_removes_permissions | ✅ PASS | Удаление должности убирает права |
| test_multiple_users_with_same_position | ✅ PASS | Несколько user одна должность |
| test_superuser_has_all_permissions | ✅ PASS | Superuser имеет все права |
| test_position_group_permissions_are_additive | ✅ PASS | Несколько групп складываются |
| test_get_all_permissions_includes_position_perms | ✅ PASS | get_all_permissions() работает |
| test_position_without_groups_grants_no_permissions | ✅ PASS | Должность без групп = нет прав |
| test_inactive_user_has_no_permissions | ✅ PASS | Неактивный user = нет прав |
| test_position_permissions_with_direct_user_permissions | ✅ PASS | Прямые + должность аддитивны |
| test_feed_permissions_through_position | ✅ PASS | Реальные feed права работают |
| test_documents_permissions_through_position | ✅ PASS | Реальные document права работают |
### 🎯 Рекомендации
1. **Запустить отладочный скрипт** (Шаг 1) чтобы увидеть текущее состояние
2. **Проверить LDAP синхронизацию** группы для должности
3. **Очистить кэш** и перелогинить пользователя
4. **Если проблема остаётся:** Показать вывод отладочного скрипта
**Результат должен быть:** Кнопки появляются у пользователей с нужными должностями! 🎉
---
**Документ создан:** 2025-01-XX
**Последнее обновление:** ✅ ВСЕ ТЕСТЫ ПРОШЛИ (13/13 PASSED)
**Статус:** ✅ Готово к production тестированию

View File

@ -1,267 +0,0 @@
============================= test session starts =============================
platform win32 -- Python 3.12.10, pytest-8.4.1, pluggy-1.6.0 -- C:\Users\nadeini\Dev\EUSRR\.venv\Scripts\python.exe
cachedir: .pytest_cache
django: version: 5.2.4, settings: eusrr_backend.settings (from ini)
rootdir: C:\Users\nadeini\Dev\EUSRR\backend
configfile: pytest.ini
plugins: anyio-4.12.0, django-4.11.1
collecting ... collected 19 items
tests/api/auth/test_auth_and_registration.py::test_register_success_sends_email_and_user_inactive FAILED [ 5%]
tests/api/auth/test_auth_and_registration.py::test_register_missing_required_field[first_name] PASSED [ 10%]
tests/api/auth/test_auth_and_registration.py::test_register_missing_required_field[last_name] PASSED [ 15%]
tests/api/auth/test_auth_and_registration.py::test_register_missing_required_field[phone_number] PASSED [ 21%]
tests/api/auth/test_auth_and_registration.py::test_register_missing_required_field[email] PASSED [ 26%]
tests/api/auth/test_auth_and_registration.py::test_register_missing_required_field[password] PASSED [ 31%]
tests/api/auth/test_auth_and_registration.py::test_register_missing_required_field[birth_date] PASSED [ 36%]
tests/api/auth/test_auth_and_registration.py::test_register_requires_at_least_one_contact PASSED [ 42%]
tests/api/auth/test_auth_and_registration.py::test_register_birth_date_must_be_valid_date PASSED [ 47%]
tests/api/auth/test_auth_and_registration.py::test_register_duplicate_email_phone FAILED [ 52%]
tests/api/auth/test_auth_and_registration.py::test_register_accepts_optional_fields FAILED [ 57%]
tests/api/auth/test_auth_and_registration.py::test_verify_email_success_activates_user FAILED [ 63%]
tests/api/auth/test_auth_and_registration.py::test_verify_email_wrong_code FAILED [ 68%]
tests/api/auth/test_auth_and_registration.py::test_resend_email_sends_new_code_and_old_becomes_invalid FAILED [ 73%]
tests/api/auth/test_auth_and_registration.py::test_verify_expired_more_than_5_minutes_deletes_account FAILED [ 78%]
tests/api/auth/test_auth_and_registration.py::test_login_by_email_denied_before_verify FAILED [ 84%]
tests/api/auth/test_auth_and_registration.py::test_login_by_email_allowed_after_verify FAILED [ 89%]
tests/api/auth/test_auth_and_registration.py::test_login_by_phone_allowed_after_verify FAILED [ 94%]
tests/api/auth/test_auth_and_registration.py::test_login_wrong_password FAILED [100%]
================================== FAILURES ===================================
_____________ test_register_success_sends_email_and_user_inactive _____________
api = <rest_framework.test.APIClient object at 0x0000013601BABB30>
def test_register_success_sends_email_and_user_inactive(api):
resp = register(api)
> assert resp.status_code == status.HTTP_201_CREATED
E assert 400 == 201
E + where 400 = <Response status_code=400, "application/json">.status_code
E + and 201 = status.HTTP_201_CREATED
tests\api\auth\test_auth_and_registration.py:102: AssertionError
---------------------------- Captured stderr setup ----------------------------
Creating test database for alias 'default'...
---------------------------- Captured stderr call -----------------------------
[WARNING] 2026-01-05 07:59:47,115 django.request:253 Bad Request: /api/v1/auth/register/
------------------------------ Captured log call ------------------------------
WARNING api.v1.employees.views:views.py:368 [REGISTER] Received data: {'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', 'phone_number': '+79990000001', 'email': 'ivan@example.com', 'password': 'Str0ngPass!', 'birth_date': '1990-01-01', 'telegram': '@ivan'}
WARNING api.v1.employees.views:views.py:369 [REGISTER] Content-Type: application/json
WARNING api.v1.employees.views:views.py:387 [REGISTER] Validation errors: {'avatar': [ErrorDetail(string='Íè îäíîãî ôàéëà íå áûëî îòïðàâëåíî.', code='required')], 'gender': [ErrorDetail(string='Ïîëå "Ïîë" îáÿçàòåëüíî äëÿ çàïîëíåíèÿ.', code='required')]}
_____________________ test_register_duplicate_email_phone _____________________
api = <rest_framework.test.APIClient object at 0x0000013601C54B30>
def test_register_duplicate_email_phone(api):
# 1) Ðåãèñòðèðóåì ïåðâîãî ïîëüçîâàòåëÿ (ïîêà íå ïîäòâåðæä¸í)
payload = register_payload()
> assert register(api, **payload).status_code == status.HTTP_201_CREATED
E assert 400 == 201
E + where 400 = <Response status_code=400, "application/json">.status_code
E + where <Response status_code=400, "application/json"> = register(<rest_framework.test.APIClient object at 0x0000013601C54B30>, **{'birth_date': '1990-01-01', 'email': 'ivan@example.com', 'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', ...})
E + and 201 = status.HTTP_201_CREATED
tests\api\auth\test_auth_and_registration.py:149: AssertionError
---------------------------- Captured stderr call -----------------------------
[WARNING] 2026-01-05 07:59:47,550 django.request:253 Bad Request: /api/v1/auth/register/
------------------------------ Captured log call ------------------------------
WARNING api.v1.employees.views:views.py:368 [REGISTER] Received data: {'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', 'phone_number': '+79990000001', 'email': 'ivan@example.com', 'password': 'Str0ngPass!', 'birth_date': '1990-01-01', 'telegram': '@ivan'}
WARNING api.v1.employees.views:views.py:369 [REGISTER] Content-Type: application/json
WARNING api.v1.employees.views:views.py:387 [REGISTER] Validation errors: {'avatar': [ErrorDetail(string='Íè îäíîãî ôàéëà íå áûëî îòïðàâëåíî.', code='required')], 'gender': [ErrorDetail(string='Ïîëå "Ïîë" îáÿçàòåëüíî äëÿ çàïîëíåíèÿ.', code='required')]}
____________________ test_register_accepts_optional_fields ____________________
api = <rest_framework.test.APIClient object at 0x0000013601C56780>
def test_register_accepts_optional_fields(api):
resp = register(
api,
patronymic="Ïåòðîâè÷",
gender=1,
whatsapp="+79995550011", # ìîæíî óêàçàòü äðóãîé êîíòàêò
telegram="", # òåëåãðàì ìîæíî íå óêàçûâàòü, ò.ê. whatsapp åñòü
)
> assert resp.status_code == status.HTTP_201_CREATED
E assert 400 == 201
E + where 400 = <Response status_code=400, "application/json">.status_code
E + and 201 = status.HTTP_201_CREATED
tests\api\auth\test_auth_and_registration.py:183: AssertionError
---------------------------- Captured stderr call -----------------------------
[WARNING] 2026-01-05 07:59:47,563 django.request:253 Bad Request: /api/v1/auth/register/
------------------------------ Captured log call ------------------------------
WARNING api.v1.employees.views:views.py:368 [REGISTER] Received data: {'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', 'phone_number': '+79990000001', 'email': 'ivan@example.com', 'password': 'Str0ngPass!', 'birth_date': '1990-01-01', 'telegram': '', 'patronymic': 'Ïåòðîâè÷', 'gender': 1, 'whatsapp': '+79995550011'}
WARNING api.v1.employees.views:views.py:369 [REGISTER] Content-Type: application/json
WARNING api.v1.employees.views:views.py:387 [REGISTER] Validation errors: {'avatar': [ErrorDetail(string='Íè îäíîãî ôàéëà íå áûëî îòïðàâëåíî.', code='required')]}
__________________ test_verify_email_success_activates_user ___________________
api = <rest_framework.test.APIClient object at 0x0000013601C54530>
def test_verify_email_success_activates_user(api):
> assert register(api).status_code == status.HTTP_201_CREATED
E assert 400 == 201
E + where 400 = <Response status_code=400, "application/json">.status_code
E + where <Response status_code=400, "application/json"> = register(<rest_framework.test.APIClient object at 0x0000013601C54530>)
E + and 201 = status.HTTP_201_CREATED
tests\api\auth\test_auth_and_registration.py:190: AssertionError
---------------------------- Captured stderr call -----------------------------
[WARNING] 2026-01-05 07:59:47,574 django.request:253 Bad Request: /api/v1/auth/register/
------------------------------ Captured log call ------------------------------
WARNING api.v1.employees.views:views.py:368 [REGISTER] Received data: {'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', 'phone_number': '+79990000001', 'email': 'ivan@example.com', 'password': 'Str0ngPass!', 'birth_date': '1990-01-01', 'telegram': '@ivan'}
WARNING api.v1.employees.views:views.py:369 [REGISTER] Content-Type: application/json
WARNING api.v1.employees.views:views.py:387 [REGISTER] Validation errors: {'avatar': [ErrorDetail(string='Íè îäíîãî ôàéëà íå áûëî îòïðàâëåíî.', code='required')], 'gender': [ErrorDetail(string='Ïîëå "Ïîë" îáÿçàòåëüíî äëÿ çàïîëíåíèÿ.', code='required')]}
________________________ test_verify_email_wrong_code _________________________
api = <rest_framework.test.APIClient object at 0x0000013601DB01A0>
def test_verify_email_wrong_code(api):
> assert register(api).status_code == status.HTTP_201_CREATED
E assert 400 == 201
E + where 400 = <Response status_code=400, "application/json">.status_code
E + where <Response status_code=400, "application/json"> = register(<rest_framework.test.APIClient object at 0x0000013601DB01A0>)
E + and 201 = status.HTTP_201_CREATED
tests\api\auth\test_auth_and_registration.py:202: AssertionError
---------------------------- Captured stderr call -----------------------------
[WARNING] 2026-01-05 07:59:47,586 django.request:253 Bad Request: /api/v1/auth/register/
------------------------------ Captured log call ------------------------------
WARNING api.v1.employees.views:views.py:368 [REGISTER] Received data: {'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', 'phone_number': '+79990000001', 'email': 'ivan@example.com', 'password': 'Str0ngPass!', 'birth_date': '1990-01-01', 'telegram': '@ivan'}
WARNING api.v1.employees.views:views.py:369 [REGISTER] Content-Type: application/json
WARNING api.v1.employees.views:views.py:387 [REGISTER] Validation errors: {'avatar': [ErrorDetail(string='Íè îäíîãî ôàéëà íå áûëî îòïðàâëåíî.', code='required')], 'gender': [ErrorDetail(string='Ïîëå "Ïîë" îáÿçàòåëüíî äëÿ çàïîëíåíèÿ.', code='required')]}
__________ test_resend_email_sends_new_code_and_old_becomes_invalid ___________
api = <rest_framework.test.APIClient object at 0x0000013601CCEA50>
def test_resend_email_sends_new_code_and_old_becomes_invalid(api):
> assert register(api).status_code == status.HTTP_201_CREATED
E assert 400 == 201
E + where 400 = <Response status_code=400, "application/json">.status_code
E + where <Response status_code=400, "application/json"> = register(<rest_framework.test.APIClient object at 0x0000013601CCEA50>)
E + and 201 = status.HTTP_201_CREATED
tests\api\auth\test_auth_and_registration.py:212: AssertionError
---------------------------- Captured stderr call -----------------------------
[WARNING] 2026-01-05 07:59:47,598 django.request:253 Bad Request: /api/v1/auth/register/
------------------------------ Captured log call ------------------------------
WARNING api.v1.employees.views:views.py:368 [REGISTER] Received data: {'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', 'phone_number': '+79990000001', 'email': 'ivan@example.com', 'password': 'Str0ngPass!', 'birth_date': '1990-01-01', 'telegram': '@ivan'}
WARNING api.v1.employees.views:views.py:369 [REGISTER] Content-Type: application/json
WARNING api.v1.employees.views:views.py:387 [REGISTER] Validation errors: {'avatar': [ErrorDetail(string='Íè îäíîãî ôàéëà íå áûëî îòïðàâëåíî.', code='required')], 'gender': [ErrorDetail(string='Ïîëå "Ïîë" îáÿçàòåëüíî äëÿ çàïîëíåíèÿ.', code='required')]}
___________ test_verify_expired_more_than_5_minutes_deletes_account ___________
api = <rest_framework.test.APIClient object at 0x0000013601CCC290>
settings = <pytest_django.fixtures.SettingsWrapper object at 0x0000013601CCF2F0>
def test_verify_expired_more_than_5_minutes_deletes_account(api, settings):
# Ðåãèñòðèðóåì
> assert register(api).status_code == status.HTTP_201_CREATED
E assert 400 == 201
E + where 400 = <Response status_code=400, "application/json">.status_code
E + where <Response status_code=400, "application/json"> = register(<rest_framework.test.APIClient object at 0x0000013601CCC290>)
E + and 201 = status.HTTP_201_CREATED
tests\api\auth\test_auth_and_registration.py:239: AssertionError
---------------------------- Captured stderr call -----------------------------
[WARNING] 2026-01-05 07:59:47,610 django.request:253 Bad Request: /api/v1/auth/register/
------------------------------ Captured log call ------------------------------
WARNING api.v1.employees.views:views.py:368 [REGISTER] Received data: {'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', 'phone_number': '+79990000001', 'email': 'ivan@example.com', 'password': 'Str0ngPass!', 'birth_date': '1990-01-01', 'telegram': '@ivan'}
WARNING api.v1.employees.views:views.py:369 [REGISTER] Content-Type: application/json
WARNING api.v1.employees.views:views.py:387 [REGISTER] Validation errors: {'avatar': [ErrorDetail(string='Íè îäíîãî ôàéëà íå áûëî îòïðàâëåíî.', code='required')], 'gender': [ErrorDetail(string='Ïîëå "Ïîë" îáÿçàòåëüíî äëÿ çàïîëíåíèÿ.', code='required')]}
__________________ test_login_by_email_denied_before_verify ___________________
api = <rest_framework.test.APIClient object at 0x00000136007A18E0>
def test_login_by_email_denied_before_verify(api):
> assert register(api).status_code == status.HTTP_201_CREATED
E assert 400 == 201
E + where 400 = <Response status_code=400, "application/json">.status_code
E + where <Response status_code=400, "application/json"> = register(<rest_framework.test.APIClient object at 0x00000136007A18E0>)
E + and 201 = status.HTTP_201_CREATED
tests\api\auth\test_auth_and_registration.py:258: AssertionError
---------------------------- Captured stderr call -----------------------------
[WARNING] 2026-01-05 07:59:47,622 django.request:253 Bad Request: /api/v1/auth/register/
------------------------------ Captured log call ------------------------------
WARNING api.v1.employees.views:views.py:368 [REGISTER] Received data: {'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', 'phone_number': '+79990000001', 'email': 'ivan@example.com', 'password': 'Str0ngPass!', 'birth_date': '1990-01-01', 'telegram': '@ivan'}
WARNING api.v1.employees.views:views.py:369 [REGISTER] Content-Type: application/json
WARNING api.v1.employees.views:views.py:387 [REGISTER] Validation errors: {'avatar': [ErrorDetail(string='Íè îäíîãî ôàéëà íå áûëî îòïðàâëåíî.', code='required')], 'gender': [ErrorDetail(string='Ïîëå "Ïîë" îáÿçàòåëüíî äëÿ çàïîëíåíèÿ.', code='required')]}
__________________ test_login_by_email_allowed_after_verify ___________________
api = <rest_framework.test.APIClient object at 0x00000136007A25D0>
def test_login_by_email_allowed_after_verify(api):
> assert register(api).status_code == status.HTTP_201_CREATED
E assert 400 == 201
E + where 400 = <Response status_code=400, "application/json">.status_code
E + where <Response status_code=400, "application/json"> = register(<rest_framework.test.APIClient object at 0x00000136007A25D0>)
E + and 201 = status.HTTP_201_CREATED
tests\api\auth\test_auth_and_registration.py:265: AssertionError
---------------------------- Captured stderr call -----------------------------
[WARNING] 2026-01-05 07:59:47,634 django.request:253 Bad Request: /api/v1/auth/register/
------------------------------ Captured log call ------------------------------
WARNING api.v1.employees.views:views.py:368 [REGISTER] Received data: {'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', 'phone_number': '+79990000001', 'email': 'ivan@example.com', 'password': 'Str0ngPass!', 'birth_date': '1990-01-01', 'telegram': '@ivan'}
WARNING api.v1.employees.views:views.py:369 [REGISTER] Content-Type: application/json
WARNING api.v1.employees.views:views.py:387 [REGISTER] Validation errors: {'avatar': [ErrorDetail(string='Íè îäíîãî ôàéëà íå áûëî îòïðàâëåíî.', code='required')], 'gender': [ErrorDetail(string='Ïîëå "Ïîë" îáÿçàòåëüíî äëÿ çàïîëíåíèÿ.', code='required')]}
__________________ test_login_by_phone_allowed_after_verify ___________________
api = <rest_framework.test.APIClient object at 0x0000013601B55490>
def test_login_by_phone_allowed_after_verify(api):
# Ðåãèñòðèðóåì ñ òåëåôîíîì è ïîäòâåðæäàåì
payload = register_payload()
> assert register(api, **payload).status_code == status.HTTP_201_CREATED
E assert 400 == 201
E + where 400 = <Response status_code=400, "application/json">.status_code
E + where <Response status_code=400, "application/json"> = register(<rest_framework.test.APIClient object at 0x0000013601B55490>, **{'birth_date': '1990-01-01', 'email': 'ivan@example.com', 'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', ...})
E + and 201 = status.HTTP_201_CREATED
tests\api\auth\test_auth_and_registration.py:278: AssertionError
---------------------------- Captured stderr call -----------------------------
[WARNING] 2026-01-05 07:59:47,647 django.request:253 Bad Request: /api/v1/auth/register/
------------------------------ Captured log call ------------------------------
WARNING api.v1.employees.views:views.py:368 [REGISTER] Received data: {'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', 'phone_number': '+79990000001', 'email': 'ivan@example.com', 'password': 'Str0ngPass!', 'birth_date': '1990-01-01', 'telegram': '@ivan'}
WARNING api.v1.employees.views:views.py:369 [REGISTER] Content-Type: application/json
WARNING api.v1.employees.views:views.py:387 [REGISTER] Validation errors: {'avatar': [ErrorDetail(string='Íè îäíîãî ôàéëà íå áûëî îòïðàâëåíî.', code='required')], 'gender': [ErrorDetail(string='Ïîëå "Ïîë" îáÿçàòåëüíî äëÿ çàïîëíåíèÿ.', code='required')]}
__________________________ test_login_wrong_password __________________________
api = <rest_framework.test.APIClient object at 0x0000013601B56E40>
def test_login_wrong_password(api):
> assert register(api).status_code == status.HTTP_201_CREATED
E assert 400 == 201
E + where 400 = <Response status_code=400, "application/json">.status_code
E + where <Response status_code=400, "application/json"> = register(<rest_framework.test.APIClient object at 0x0000013601B56E40>)
E + and 201 = status.HTTP_201_CREATED
tests\api\auth\test_auth_and_registration.py:290: AssertionError
---------------------------- Captured stderr call -----------------------------
[WARNING] 2026-01-05 07:59:47,659 django.request:253 Bad Request: /api/v1/auth/register/
------------------------------ Captured log call ------------------------------
WARNING api.v1.employees.views:views.py:368 [REGISTER] Received data: {'first_name': 'Èâàí', 'last_name': 'Èâàíîâ', 'phone_number': '+79990000001', 'email': 'ivan@example.com', 'password': 'Str0ngPass!', 'birth_date': '1990-01-01', 'telegram': '@ivan'}
WARNING api.v1.employees.views:views.py:369 [REGISTER] Content-Type: application/json
WARNING api.v1.employees.views:views.py:387 [REGISTER] Validation errors: {'avatar': [ErrorDetail(string='Íè îäíîãî ôàéëà íå áûëî îòïðàâëåíî.', code='required')], 'gender': [ErrorDetail(string='Ïîëå "Ïîë" îáÿçàòåëüíî äëÿ çàïîëíåíèÿ.', code='required')]}
-------------------------- Captured stderr teardown ---------------------------
Destroying test database for alias 'default'...
============================== warnings summary ===============================
tests/api/auth/test_auth_and_registration.py::test_register_success_sends_email_and_user_inactive
C:\Users\nadeini\Dev\EUSRR\.venv\Lib\site-packages\pyasn1\codec\ber\encoder.py:952: DeprecationWarning: tagMap is deprecated. Please use TAG_MAP instead.
warnings.warn(f"{attr} is deprecated. Please use {newAttr} instead.", DeprecationWarning)
tests/api/auth/test_auth_and_registration.py::test_register_success_sends_email_and_user_inactive
C:\Users\nadeini\Dev\EUSRR\.venv\Lib\site-packages\pyasn1\codec\ber\encoder.py:952: DeprecationWarning: typeMap is deprecated. Please use TYPE_MAP instead.
warnings.warn(f"{attr} is deprecated. Please use {newAttr} instead.", DeprecationWarning)
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=========================== short test summary info ===========================
FAILED tests/api/auth/test_auth_and_registration.py::test_register_success_sends_email_and_user_inactive
FAILED tests/api/auth/test_auth_and_registration.py::test_register_duplicate_email_phone
FAILED tests/api/auth/test_auth_and_registration.py::test_register_accepts_optional_fields
FAILED tests/api/auth/test_auth_and_registration.py::test_verify_email_success_activates_user
FAILED tests/api/auth/test_auth_and_registration.py::test_verify_email_wrong_code
FAILED tests/api/auth/test_auth_and_registration.py::test_resend_email_sends_new_code_and_old_becomes_invalid
FAILED tests/api/auth/test_auth_and_registration.py::test_verify_expired_more_than_5_minutes_deletes_account
FAILED tests/api/auth/test_auth_and_registration.py::test_login_by_email_denied_before_verify
FAILED tests/api/auth/test_auth_and_registration.py::test_login_by_email_allowed_after_verify
FAILED tests/api/auth/test_auth_and_registration.py::test_login_by_phone_allowed_after_verify
FAILED tests/api/auth/test_auth_and_registration.py::test_login_wrong_password
================== 11 failed, 8 passed, 2 warnings in 7.69s ===================

View File

@ -1,16 +0,0 @@
from schedule.models import Calendar, Event
print("\n=== КАЛЕНДАРИ В БД ===")
calendars = Calendar.objects.all()
print(f"Всего календарей: {calendars.count()}")
for cal in calendars:
events_count = Event.objects.filter(calendar=cal).count()
print(f" - ID: {cal.id}, Name: {cal.name}, Slug: {cal.slug}, События: {events_count}")
print("\n=== СОБЫТИЯ В БД ===")
events = Event.objects.all()
print(f"Всего событий: {events.count()}")
for event in events[:10]: # Показываем первые 10
print(f" - ID: {event.id}, Title: {event.title}, Start: {event.start}, Calendar: {event.calendar.name if event.calendar else 'None'}")
if events.count() > 10:
print(f" ... и ещё {events.count() - 10} событий")

View File

@ -1,128 +0,0 @@
#!/usr/bin/env python
"""Создание тестовых календарей и событий для проверки UI"""
from schedule.models import Calendar, Event
from employees.models import Employee
from datetime import datetime, timedelta
import pytz
# Часовой пояс
tz = pytz.timezone('Europe/Moscow')
# Получаем первого пользователя
try:
user = Employee.objects.first()
if not user:
print("❌ Нет пользователей в системе")
exit(1)
except Exception as e:
print(f"❌ Ошибка получения пользователя: {e}")
exit(1)
print(f"✅ Используем пользователя: {user.username}")
# Создаём календари
calendars_data = [
{"name": "Рабочий календарь", "slug": "work"},
{"name": "Личные дела", "slug": "personal"},
{"name": "Встречи", "slug": "meetings"},
]
calendars = []
for data in calendars_data:
cal, created = Calendar.objects.get_or_create(
slug=data["slug"],
defaults={"name": data["name"]}
)
calendars.append(cal)
status = "создан" if created else "уже существует"
print(f"📅 Календарь '{cal.name}' ({data['slug']}): {status}")
# Создаём тестовые события
now = datetime.now(tz)
today = now.replace(hour=0, minute=0, second=0, microsecond=0)
events_data = [
# Рабочий календарь
{
"calendar": calendars[0],
"title": "Планёрка",
"description": "Еженедельное совещание команды",
"start": today.replace(hour=10, minute=0),
"end": today.replace(hour=11, minute=0),
"color_event": "#3b82f6",
},
{
"calendar": calendars[0],
"title": "Дедлайн проекта",
"description": "Завершение первого этапа",
"start": today + timedelta(days=3, hours=18),
"end": today + timedelta(days=3, hours=19),
"color_event": "#ef4444",
},
{
"calendar": calendars[0],
"title": "Код-ревью",
"description": "Проверка pull requests",
"start": today + timedelta(days=1, hours=15),
"end": today + timedelta(days=1, hours=16),
"color_event": "#8b5cf6",
},
# Личные дела
{
"calendar": calendars[1],
"title": "Тренировка в зале",
"description": "",
"start": today.replace(hour=19, minute=0),
"end": today.replace(hour=20, minute=30),
"color_event": "#10b981",
},
{
"calendar": calendars[1],
"title": "День рождения друга",
"description": "Купить подарок",
"start": today + timedelta(days=5, hours=18),
"end": today + timedelta(days=5, hours=23),
"color_event": "#f59e0b",
},
# Встречи
{
"calendar": calendars[2],
"title": "Встреча с клиентом",
"description": "Обсуждение нового проекта",
"start": today + timedelta(days=2, hours=14),
"end": today + timedelta(days=2, hours=15, minutes=30),
"color_event": "#06b6d4",
},
{
"calendar": calendars[2],
"title": "Собеседование кандидата",
"description": "Senior Python Developer",
"start": today + timedelta(days=4, hours=11),
"end": today + timedelta(days=4, hours=12),
"color_event": "#ec4899",
},
]
created_count = 0
for event_data in events_data:
event, created = Event.objects.get_or_create(
calendar=event_data["calendar"],
title=event_data["title"],
start=event_data["start"],
defaults={
"description": event_data["description"],
"end": event_data["end"],
"color_event": event_data["color_event"],
"creator": user,
}
)
if created:
created_count += 1
print(f"✅ Создано событие: {event.title} ({event_data['calendar'].name})")
print(f"\n🎉 Готово! Создано {created_count} новых событий")
print(f"📊 Всего календарей: {Calendar.objects.count()}")
print(f"📊 Всего событий: {Event.objects.count()}")