Реорганизация документации и скриптов проекта
- Создана структура docs/ с категориями: completed, guides, architecture, diagnostic, in-progress, reports - Перемещено 47+ MD файлов из корня проекта в docs/ - Удалено 6 устаревших/дублирующихся файлов - Создана структура backend/scripts/ для тестов и утилит - Перемещено 35 Python скриптов в backend/scripts/ - Создана структура backend/docs/ для технической документации - Перемещено 12 MD файлов backend в backend/docs/ - Перемещены LDAP тестовые данные в backend/tests/fixtures/ - Добавлены README файлы для навигации - Добавлены правила в .github/copilot-instructions.md для размещения документации Результат: - Корень проекта: только README.md - Корень backend/: только manage.py и Django приложения - Вся документация организована в структурированные папки
This commit is contained in:
parent
de4506ae24
commit
48631f43fd
116
.github/copilot-instructions.md
vendored
Normal file
116
.github/copilot-instructions.md
vendored
Normal file
@ -0,0 +1,116 @@
|
||||
# Инструкции для GitHub Copilot
|
||||
|
||||
## О проекте
|
||||
|
||||
**EUSRR** - корпоративная система управления на Django с интеграцией LDAP, документооборотом, мессенджером и системой заявок.
|
||||
|
||||
### Технологический стек:
|
||||
- **Backend**: Django 5.2.4, Django REST Framework, Django Channels (WebSocket)
|
||||
- **Frontend**: Bootstrap 5, Vanilla JavaScript (ES6+ модули)
|
||||
- **База данных**: PostgreSQL / SQLite (для разработки)
|
||||
- **Python**: 3.12+ (текущая версия в venv: 3.12.10)
|
||||
|
||||
### Структура проекта:
|
||||
- `backend/` - Django приложение
|
||||
- `backend/manage.py` - управление Django проектом
|
||||
- `backend/eusrr_backend/` - основные настройки
|
||||
- `backend/api/`, `employees/`, `documents/`, `feed/`, `requests_app/` и др. - Django приложения
|
||||
|
||||
## Виртуальное окружение Python
|
||||
|
||||
В этом проекте используется виртуальное окружение Python, расположенное в `.venv/`.
|
||||
|
||||
### Важные правила при работе с Python:
|
||||
|
||||
1. **Всегда используйте полный путь** к Python и pip из виртуального окружения
|
||||
2. **Никогда не используйте** команды `python` или `pip` без указания полного пути
|
||||
|
||||
### Примеры:
|
||||
|
||||
❌ **Неправильно:**
|
||||
```bash
|
||||
python manage.py runserver
|
||||
pip install django
|
||||
```
|
||||
|
||||
✅ **Правильно:**
|
||||
```bash
|
||||
.venv/Scripts/python manage.py runserver
|
||||
.venv/Scripts/pip install django
|
||||
```
|
||||
|
||||
### Пути к исполняемым файлам:
|
||||
|
||||
- Python: `.venv/Scripts/python`
|
||||
- pip: `.venv/Scripts/pip`
|
||||
|
||||
**Всегда используй эти полные пути вместо команд `python` и `pip`**
|
||||
|
||||
## Правила работы с документацией
|
||||
|
||||
### ⚠️ КРИТИЧНО: НЕ создавай новые MD файлы в корневых папках!
|
||||
|
||||
**Проблема:** Создание отчетов и документации в корне проекта или корне backend/ захламляет структуру.
|
||||
|
||||
### Правила размещения документации:
|
||||
|
||||
❌ **НЕПРАВИЛЬНО:**
|
||||
```
|
||||
PROJECT_REPORT.md # В корне проекта
|
||||
backend/AUDIT_REPORT.md # В корне backend/
|
||||
backend/CLEANUP_SUMMARY.md # В корне backend/
|
||||
```
|
||||
|
||||
✅ **ПРАВИЛЬНО:**
|
||||
```
|
||||
docs/reports/PROJECT_REPORT.md # Отчеты в специальной папке
|
||||
backend/docs/reports/AUDIT_REPORT.md # Отчеты backend в своей папке
|
||||
docs/in-progress/TASK_PROGRESS.md # Активные задачи
|
||||
```
|
||||
|
||||
### Структура для новой документации:
|
||||
|
||||
**Корневая документация проекта:**
|
||||
- `docs/completed/` - завершенные задачи и фичи
|
||||
- `docs/guides/` - руководства разработчика
|
||||
- `docs/architecture/` - архитектурная документация
|
||||
- `docs/diagnostic/` - диагностические гайды
|
||||
- `docs/in-progress/` - активные задачи
|
||||
- `docs/reports/` - отчеты и аудиты
|
||||
|
||||
**Backend документация:**
|
||||
- `backend/docs/guides/` - руководства по backend
|
||||
- `backend/docs/troubleshooting/` - решение проблем
|
||||
- `backend/docs/architecture/` - архитектура backend
|
||||
- `backend/docs/testing/` - тестовая документация
|
||||
- `backend/docs/reports/` - отчеты и аудиты backend
|
||||
|
||||
### Когда создаешь новый MD файл:
|
||||
|
||||
1. **Определи тип документа:**
|
||||
- Отчет/аудит → `docs/reports/` или `backend/docs/reports/`
|
||||
- Гайд → `docs/guides/` или `backend/docs/guides/`
|
||||
- Завершенная задача → `docs/completed/`
|
||||
- Активная задача → `docs/in-progress/`
|
||||
|
||||
2. **НИКОГДА не создавай в корне проекта или корне backend/**
|
||||
|
||||
3. **Исключения (единственные файлы в корне):**
|
||||
- `README.md` - главная документация (уже существует)
|
||||
- Только по явному запросу пользователя
|
||||
|
||||
### Примеры правильного создания:
|
||||
|
||||
```python
|
||||
# Отчет об аудите backend
|
||||
create_file("backend/docs/reports/CLEANUP_AUDIT.md", ...)
|
||||
|
||||
# Отчет о реорганизации docs
|
||||
create_file("docs/reports/REORGANIZATION_REPORT.md", ...)
|
||||
|
||||
# Новый гайд
|
||||
create_file("docs/guides/NEW_FEATURE_GUIDE.md", ...)
|
||||
|
||||
# Прогресс активной задачи
|
||||
create_file("docs/in-progress/FEATURE_X_PROGRESS.md", ...)
|
||||
```
|
||||
@ -1,143 +0,0 @@
|
||||
# Рефакторинг base.html
|
||||
|
||||
## Проблема
|
||||
`base.html` содержит много chat-специфичной логики, которая должна быть только на страницах чатов.
|
||||
|
||||
## Принципы рефакторинга
|
||||
|
||||
### ✅ Что ДОЛЖНО быть в base.html (универсальное):
|
||||
1. **Базовая структура** - navbar, sidebar, rightbar, main
|
||||
2. **Глобальные утилиты** - stringUtils, timing, scroll
|
||||
3. **Глобальные переменные** - `window.currentUserId`
|
||||
4. **Универсальный WebSocket** - подключение к `ws/`, но БЕЗ chat-специфичной логики
|
||||
5. **Notification Manager** - уведомления нужны везде
|
||||
6. **Theme Initializer** - тема нужна везде
|
||||
7. **Bootstrap, Icons** - базовые библиотеки
|
||||
|
||||
### ❌ Что НУЖНО убрать из base.html (chat-специфичное):
|
||||
1. **chatAvatarMap** - нужен только на страницах чатов
|
||||
2. **chatMarkRead** - только для chat_detail.html
|
||||
3. **chatListFilter** - только для chat_list.html
|
||||
4. **chatBadgeManager** - только для страниц с чатами
|
||||
5. **chatListRealtime** - только для chat_list.html
|
||||
6. **Sidebar badge logic** - должна быть в отдельном модуле
|
||||
7. **Chat-специфичные обработчики в userWebSocket** - `onListUpdate`, `markReadApi`
|
||||
|
||||
## План действий
|
||||
|
||||
### Шаг 1: Создать chat-специфичные include файлы
|
||||
|
||||
**`includes/chat_scripts.html`** - общие скрипты для всех страниц чатов:
|
||||
```django
|
||||
{# Загружается на chat_list.html и chat_detail.html #}
|
||||
- chatAvatarMap
|
||||
- chatBadgeManager
|
||||
- userWebSocket инициализация с chat-обработчиками
|
||||
- Sidebar badge logic
|
||||
```
|
||||
|
||||
**`includes/chat_detail_scripts.html`** - только для chat_detail.html:
|
||||
```django
|
||||
- chatMarkRead
|
||||
- chatComposer
|
||||
- chatHistoryLoader
|
||||
- chat-detail-enhanced
|
||||
```
|
||||
|
||||
**`includes/chat_list_scripts.html`** - только для chat_list.html:
|
||||
```django
|
||||
- chatListFilter
|
||||
- chatListRealtime
|
||||
```
|
||||
|
||||
### Шаг 2: Упростить base.html
|
||||
|
||||
**Что остаётся:**
|
||||
```javascript
|
||||
// Глобальные утилиты
|
||||
import { esc, norm, ... } from 'stringUtils.js';
|
||||
import { debounce, throttle } from 'timing.js';
|
||||
import { smoothScrollTo, ... } from 'scroll.js';
|
||||
|
||||
// Базовые компоненты
|
||||
import { initNavbarHeight } from 'navbarHeight.js';
|
||||
import { initLikeHandler } from 'likeHandler.js';
|
||||
|
||||
// Универсальный WebSocket (БЕЗ chat-специфики)
|
||||
import { initUserWebSocket } from 'userWebSocket.js';
|
||||
|
||||
// Базовая инициализация
|
||||
const userWs = initUserWebSocket({
|
||||
userId: {{ user.id }}
|
||||
});
|
||||
window.userWebSocket = userWs;
|
||||
```
|
||||
|
||||
### Шаг 3: Обновить chat_detail.html
|
||||
|
||||
```django
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% include "includes/chat_scripts.html" %}
|
||||
{% include "includes/chat_detail_scripts.html" %}
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
### Шаг 4: Обновить chat_list.html
|
||||
|
||||
```django
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block extra_js %}
|
||||
{% include "includes/chat_scripts.html" %}
|
||||
{% include "includes/chat_list_scripts.html" %}
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
## Результат
|
||||
|
||||
### До:
|
||||
- ❌ base.html: 250 строк, много chat-логики
|
||||
- ❌ Все chat-модули загружаются на всех страницах
|
||||
- ❌ Сложно понять, что где используется
|
||||
|
||||
### После:
|
||||
- ✅ base.html: ~120 строк, только универсальное
|
||||
- ✅ Chat-модули загружаются только на нужных страницах
|
||||
- ✅ Чёткое разделение ответственности
|
||||
- ✅ Легко добавлять новые модули
|
||||
|
||||
## Архитектура модулей
|
||||
|
||||
```
|
||||
base.html (универсальные модули)
|
||||
├─ stringUtils.js
|
||||
├─ timing.js
|
||||
├─ scroll.js
|
||||
├─ navbarHeight.js
|
||||
├─ likeHandler.js
|
||||
├─ userWebSocket.js (базовое подключение)
|
||||
└─ notification-manager.js
|
||||
|
||||
includes/chat_scripts.html (общее для всех chat-страниц)
|
||||
├─ chatAvatarMap.js
|
||||
├─ chatBadgeManager.js
|
||||
├─ sidebarBadge.js (новый модуль)
|
||||
└─ userWebSocket.js (расширенная инициализация с chat-обработчиками)
|
||||
|
||||
includes/chat_detail_scripts.html (только для chat_detail.html)
|
||||
├─ chatMarkRead.js
|
||||
├─ chatComposer.js
|
||||
├─ chatHistoryLoader.js
|
||||
└─ chat-detail-enhanced.js
|
||||
├─ messageReactions.js
|
||||
├─ messageContextMenu.js
|
||||
├─ messageSelection.js
|
||||
├─ chatPoll.js
|
||||
└─ messageEditing.js
|
||||
|
||||
includes/chat_list_scripts.html (только для chat_list.html)
|
||||
├─ chatListFilter.js
|
||||
└─ chatListRealtime.js
|
||||
```
|
||||
@ -1,426 +0,0 @@
|
||||
# Анализ рендеринга списка чатов и план рефакторинга
|
||||
|
||||
## 🔍 Текущая архитектура chat_list.html
|
||||
|
||||
### Проблемы
|
||||
|
||||
#### 1. **Огромное дублирование разметки**
|
||||
|
||||
**Проблема:**
|
||||
- 1058 строк в шаблоне
|
||||
- **6 циклов `{% for chat in chats %}`** для разных типов
|
||||
- Почти идентичная разметка повторяется 6 раз
|
||||
- Каждый тип (global, department, private, group, channel, announcement) имеет свой блок
|
||||
|
||||
**Пример дублирования:**
|
||||
```django
|
||||
{# GLOBAL - строки 82-149 #}
|
||||
{% for chat in chats %}
|
||||
{% if chat.type == 'global' %}
|
||||
<div class="chat-row" data-chat-id="{{ chat.pk }}" data-type="global">
|
||||
<!-- 70 строк разметки -->
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{# DEPARTMENT - строки 163-246 #}
|
||||
{% for chat in chats %}
|
||||
{% if chat.type == 'department' %}
|
||||
<div class="chat-row" data-chat-id="{{ chat.pk }}" data-type="department">
|
||||
<!-- Почти те же 70 строк с небольшими отличиями -->
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{# ... и так для каждого типа #}
|
||||
```
|
||||
|
||||
**Количество дублирования:**
|
||||
- Базовая структура `.chat-row`: **6 раз**
|
||||
- Разметка аватара: **6 раз** (с разными условиями)
|
||||
- Разметка названия: **6 раз**
|
||||
- Разметка последнего сообщения: **6 раз**
|
||||
- Меню действий: **6 раз**
|
||||
|
||||
#### 2. **Неэффективный серверный рендеринг**
|
||||
|
||||
**Проблема:**
|
||||
```python
|
||||
# ChatListView.get_queryset() - 180 строк SQL логики
|
||||
qs = Chat.objects.filter(
|
||||
Q(type="global") |
|
||||
Q(type="department", department__in=departments) |
|
||||
Q(type="private", participants=user) |
|
||||
Q(id__in=membership_chat_ids)
|
||||
).annotate(
|
||||
last_msg_at=Subquery(last_qs),
|
||||
last_read_at=Coalesce(...),
|
||||
unread_count=Count("messages", filter=...)
|
||||
).prefetch_related(...)
|
||||
```
|
||||
|
||||
**Последствия:**
|
||||
- Сервер делает **сложные SQL-запросы с подзапросами**
|
||||
- Сервер рендерит **весь HTML** для всех чатов
|
||||
- При 50 чатах = **50 × 70 строк** = 3500 строк HTML
|
||||
- Нет кэширования на клиенте
|
||||
- При каждом обновлении страницы - полный перерендер
|
||||
|
||||
#### 3. **Смешанная ответственность**
|
||||
|
||||
**Проблема:**
|
||||
- Сервер считает `unread_count`, `last_msg_at`
|
||||
- Сервер рендерит HTML
|
||||
- Клиент обновляет через `chatListRealtime.js`
|
||||
- **Дублирование логики:** сервер рендерит, клиент тоже умеет обновлять
|
||||
|
||||
**Пример:**
|
||||
```django
|
||||
{# Серверный рендеринг #}
|
||||
<span data-last-time>{{ last.created_at|date:"d.m.Y H:i" }}</span>
|
||||
<strong data-last-author>{{ last.author.get_full_name }}</strong>:
|
||||
<span data-last-preview>{{ last.content|truncatechars:120 }}</span>
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Клиентское обновление (chatListRealtime.js)
|
||||
function updateChatCard(chatId, message) {
|
||||
const row = document.querySelector(`[data-chat-id="${chatId}"]`);
|
||||
const lastTime = row.querySelector('[data-last-time]');
|
||||
const lastAuthor = row.querySelector('[data-last-author]');
|
||||
const lastPreview = row.querySelector('[data-last-preview]');
|
||||
|
||||
lastTime.textContent = formatTime(message.created_at);
|
||||
lastAuthor.textContent = message.author_name;
|
||||
lastPreview.textContent = truncate(message.content, 120);
|
||||
}
|
||||
```
|
||||
|
||||
**Два шаблона для одного:**
|
||||
- Django template: `{{ last.content|truncatechars:120 }}`
|
||||
- JavaScript: `truncate(message.content, 120)`
|
||||
|
||||
---
|
||||
|
||||
## 📊 Статистика текущего подхода
|
||||
|
||||
### Размер файлов
|
||||
|
||||
| Файл | Строк | Дублирование |
|
||||
|------|-------|--------------|
|
||||
| `chat_list.html` | 1058 | ~70% повторяющегося кода |
|
||||
| `ChatListView.get_queryset()` | 180 | Сложная SQL логика |
|
||||
| `chatListRealtime.js` | ~200 | Дублирует логику шаблона |
|
||||
|
||||
### Производительность
|
||||
|
||||
| Метрика | Значение | Проблема |
|
||||
|---------|----------|----------|
|
||||
| **HTML размер** (50 чатов) | ~150 KB | Весь HTML генерируется на сервере |
|
||||
| **SQL запросы** | 5-10 | С подзапросами для unread_count |
|
||||
| **Время рендеринга** | 200-500ms | Зависит от кол-ва чатов |
|
||||
| **Кэширование** | ❌ Нет | При каждом заходе - полный рендер |
|
||||
|
||||
### Масштабируемость
|
||||
|
||||
| Количество чатов | HTML размер | Время загрузки |
|
||||
|------------------|-------------|----------------|
|
||||
| 10 чатов | ~30 KB | ~100ms |
|
||||
| 50 чатов | ~150 KB | ~500ms |
|
||||
| 100 чатов | ~300 KB | ~1s |
|
||||
| 500 чатов | ~1.5 MB | ~5s ⚠️ |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 План рефакторинга
|
||||
|
||||
### Вариант 1: Минимальный (Django Include)
|
||||
|
||||
**Идея:** Вынести разметку карточки чата в отдельный include
|
||||
|
||||
```django
|
||||
{# includes/components/chat_card.html #}
|
||||
{% load static %}
|
||||
|
||||
<div class="chat-row d-flex align-items-start gap-2 py-2 px-2"
|
||||
data-chat-id="{{ chat.pk }}"
|
||||
data-type="{{ chat.type }}"
|
||||
data-last-ts="{% if last %}{{ last.created_at|date:'U' }}000{% endif %}">
|
||||
|
||||
{# Аватар #}
|
||||
<div class="card-icon" style="width:44px;height:44px;">
|
||||
{% include "includes/components/chat_avatar.html" with chat=chat %}
|
||||
</div>
|
||||
|
||||
{# Контент #}
|
||||
<div class="flex-grow-1 position-relative">
|
||||
{% include "includes/components/chat_content.html" with chat=chat last=last %}
|
||||
</div>
|
||||
|
||||
{# Меню #}
|
||||
<div class="chat-actions-menu position-relative" style="z-index:2;">
|
||||
{% include "includes/components/chat_menu.html" with chat=chat %}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Использование:**
|
||||
```django
|
||||
{# chat_list.html упрощается до #}
|
||||
{% for chat in chats %}
|
||||
{% if chat.type == 'global' %}
|
||||
{% include "includes/components/chat_card.html" with chat=chat last=chat.last_message.0 %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
```
|
||||
|
||||
**Результат:**
|
||||
- ✅ Устранение дублирования (1058 строк → ~400 строк)
|
||||
- ✅ Единый шаблон карточки
|
||||
- ⚠️ Всё ещё серверный рендеринг
|
||||
- ⚠️ Не решает проблему производительности
|
||||
|
||||
---
|
||||
|
||||
### Вариант 2: Гибридный (API + клиентский рендеринг)
|
||||
|
||||
**Идея:** Сервер отдаёт JSON, клиент рендерит
|
||||
|
||||
**Backend:**
|
||||
```python
|
||||
class ChatListAPIView(APIView):
|
||||
def get(self, request):
|
||||
user = request.user
|
||||
chats = Chat.objects.filter(
|
||||
# ... фильтры
|
||||
).values(
|
||||
'id', 'type', 'name', 'avatar',
|
||||
'last_message', 'unread_count',
|
||||
'participants' # для private
|
||||
)
|
||||
|
||||
return Response({
|
||||
'chats': list(chats),
|
||||
'user_id': user.id
|
||||
})
|
||||
```
|
||||
|
||||
**Frontend:**
|
||||
```javascript
|
||||
// chat_list.html теперь содержит только контейнер
|
||||
<div id="chatListContainer"></div>
|
||||
|
||||
<script type="module">
|
||||
import { renderChatList } from '{% static "js/components/chatListRenderer.js" %}';
|
||||
|
||||
// Загружаем данные
|
||||
const response = await fetch('/api/v1/chats/');
|
||||
const { chats } = await response.json();
|
||||
|
||||
// Рендерим на клиенте
|
||||
renderChatList(chats, {
|
||||
container: '#chatListContainer',
|
||||
onChatClick: (chatId) => {
|
||||
window.location.href = `/communications/chats/${chatId}/`;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
**Результат:**
|
||||
- ✅ Единый шаблон (JS функция)
|
||||
- ✅ Быстрая загрузка (JSON легче HTML)
|
||||
- ✅ Можно кэшировать на клиенте
|
||||
- ✅ Лёгкие обновления (только данные)
|
||||
- ⚠️ Требует переписать рендеринг на JS
|
||||
|
||||
---
|
||||
|
||||
### Вариант 3: Полный (React/Vue компоненты)
|
||||
|
||||
**Идея:** Компонентный подход с реактивностью
|
||||
|
||||
**Структура:**
|
||||
```
|
||||
<ChatList>
|
||||
├─ <ChatSection type="global">
|
||||
│ └─ <ChatCard chat={...} />
|
||||
├─ <ChatSection type="department">
|
||||
│ └─ <ChatCard chat={...} />
|
||||
└─ <ChatSection type="private">
|
||||
└─ <ChatCard chat={...} />
|
||||
```
|
||||
|
||||
**React компонент:**
|
||||
```jsx
|
||||
// components/ChatCard.jsx
|
||||
const ChatCard = ({ chat }) => {
|
||||
const { id, type, name, avatar, lastMessage, unreadCount } = chat;
|
||||
|
||||
return (
|
||||
<div className="chat-row" data-chat-id={id} data-type={type}>
|
||||
<div className="card-icon">
|
||||
<ChatAvatar chat={chat} />
|
||||
</div>
|
||||
<div className="flex-grow-1">
|
||||
<ChatTitle chat={chat} />
|
||||
<ChatLastMessage message={lastMessage} />
|
||||
</div>
|
||||
<ChatMenu chat={chat} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// components/ChatList.jsx
|
||||
const ChatList = () => {
|
||||
const { chats, loading } = useChatList();
|
||||
|
||||
if (loading) return <Spinner />;
|
||||
|
||||
return (
|
||||
<div className="chat-list-main">
|
||||
{CHAT_TYPES.map(type => (
|
||||
<ChatSection
|
||||
key={type}
|
||||
type={type}
|
||||
chats={chats.filter(c => c.type === type)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
**Результат:**
|
||||
- ✅ Полная модульность
|
||||
- ✅ Компонентный подход
|
||||
- ✅ Реактивность (авто-обновления)
|
||||
- ✅ Виртуальный скролл (для тысяч чатов)
|
||||
- ✅ TypeScript типизация
|
||||
- ⚠️ Требует миграции на фреймворк
|
||||
- ⚠️ Большая работа (1-2 недели)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Рекомендуемый план
|
||||
|
||||
### Фаза 1: Быстрые улучшения (1-2 дня)
|
||||
|
||||
**Цель:** Устранить дублирование без переписывания архитектуры
|
||||
|
||||
1. **Создать Django includes**
|
||||
- `includes/components/chat_card.html`
|
||||
- `includes/components/chat_avatar.html`
|
||||
- `includes/components/chat_content.html`
|
||||
- `includes/components/chat_menu.html`
|
||||
|
||||
2. **Рефакторить chat_list.html**
|
||||
- Использовать includes вместо дублирования
|
||||
- Сократить с 1058 до ~300 строк
|
||||
|
||||
**Результат:**
|
||||
- ✅ 70% меньше кода
|
||||
- ✅ Легче поддерживать
|
||||
- ✅ Не ломает существующую функциональность
|
||||
|
||||
---
|
||||
|
||||
### Фаза 2: API + клиентский рендеринг (3-5 дней)
|
||||
|
||||
**Цель:** Ускорить загрузку и обновления
|
||||
|
||||
1. **Создать API endpoint**
|
||||
```python
|
||||
# api/v1/chats/
|
||||
GET /api/v1/chats/ → JSON с чатами
|
||||
```
|
||||
|
||||
2. **Создать JS рендерер**
|
||||
```javascript
|
||||
// chatListRenderer.js
|
||||
export function renderChatList(chats, options) {
|
||||
// Рендерит чаты на клиенте
|
||||
}
|
||||
```
|
||||
|
||||
3. **Унифицировать обновления**
|
||||
- Один шаблон для начального рендера
|
||||
- Один шаблон для обновлений через WebSocket
|
||||
|
||||
**Результат:**
|
||||
- ✅ Быстрее загрузка (JSON легче HTML)
|
||||
- ✅ Легче обновления (только данные)
|
||||
- ✅ Можно кэшировать
|
||||
|
||||
---
|
||||
|
||||
### Фаза 3: Компонентный подход (1-2 недели)
|
||||
|
||||
**Цель:** Современная архитектура
|
||||
|
||||
1. **Выбрать фреймворк**
|
||||
- React (рекомендуется)
|
||||
- Vue
|
||||
- Lit (Web Components)
|
||||
|
||||
2. **Создать компоненты**
|
||||
- `ChatList`, `ChatSection`, `ChatCard`
|
||||
- `ChatAvatar`, `ChatMenu`, `ChatBadge`
|
||||
|
||||
3. **State management**
|
||||
- Zustand или Redux
|
||||
- Real-time обновления через WebSocket
|
||||
|
||||
**Результат:**
|
||||
- ✅ Современная архитектура
|
||||
- ✅ Легко масштабировать
|
||||
- ✅ Отличная производительность
|
||||
|
||||
---
|
||||
|
||||
## 📋 Сравнение вариантов
|
||||
|
||||
| Критерий | Текущий | Includes | API + JS | React/Vue |
|
||||
|----------|---------|----------|----------|-----------|
|
||||
| **Дублирование** | ❌ 70% | ✅ 0% | ✅ 0% | ✅ 0% |
|
||||
| **Размер HTML** | ❌ 150KB | ❌ 150KB | ✅ 30KB | ✅ 5KB |
|
||||
| **Время загрузки** | ❌ 500ms | ❌ 500ms | ✅ 200ms | ✅ 100ms |
|
||||
| **Кэширование** | ❌ Нет | ❌ Нет | ✅ Да | ✅ Да |
|
||||
| **Обновления** | ⚠️ Сложно | ⚠️ Сложно | ✅ Легко | ✅ Легко |
|
||||
| **Сложность** | - | 🟢 Легко | 🟡 Средне | 🔴 Сложно |
|
||||
| **Время** | - | 1-2 дня | 3-5 дней | 1-2 недели |
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Что делать ПРЯМО СЕЙЧАС?
|
||||
|
||||
### Рекомендация: **Фаза 1 (Includes)**
|
||||
|
||||
**Почему:**
|
||||
- ✅ Быстро (1-2 дня)
|
||||
- ✅ Устраняет главную проблему (дублирование)
|
||||
- ✅ Не ломает существующую функциональность
|
||||
- ✅ Хорошая подготовка к Фазе 2
|
||||
|
||||
**Следующий шаг:**
|
||||
1. Создать `includes/components/chat_card.html`
|
||||
2. Вынести общую разметку
|
||||
3. Обновить `chat_list.html` для использования include
|
||||
4. Протестировать
|
||||
|
||||
**После Фазы 1:**
|
||||
- Можно сразу перейти к Фазе 2 (API + JS)
|
||||
- Или остановиться, если результат устраивает
|
||||
|
||||
---
|
||||
|
||||
## 💡 Итого
|
||||
|
||||
**Главная проблема:** Огромное дублирование разметки (6 раз повторяется одна и та же структура)
|
||||
|
||||
**Решение:**
|
||||
1. **Краткосрочное:** Django includes для устранения дублирования
|
||||
2. **Среднесрочное:** API + клиентский рендеринг
|
||||
3. **Долгосрочное:** Компонентный подход (React/Vue)
|
||||
|
||||
**Готов начать с Фазы 1?** Создам includes и рефакторну `chat_list.html`! 🚀
|
||||
@ -1,494 +0,0 @@
|
||||
# Анализ модального окна создания документов
|
||||
|
||||
## 📋 Проверка соответствия Модель → API → Модал → JavaScript
|
||||
|
||||
---
|
||||
|
||||
## ✅ **Модель Document**
|
||||
|
||||
### Поля модели (`documents/models.py`):
|
||||
```python
|
||||
class Document(models.Model):
|
||||
title = models.CharField(max_length=255) # Обязательное
|
||||
file = models.FileField(upload_to='...') # Обязательное
|
||||
description = models.TextField(blank=True) # Необязательное
|
||||
uploaded_by = models.ForeignKey(...) # Автоматическое
|
||||
uploaded_at = models.DateTimeField(auto_now_add=True) # Автоматическое
|
||||
sent_to_all = models.BooleanField(default=True) # ✅ ИСПРАВЛЕНО: default=True
|
||||
departments = models.ManyToManyField(blank=True) # Необязательное
|
||||
recipients = models.ManyToManyField(blank=True) # Необязательное
|
||||
```
|
||||
|
||||
**Изменения:**
|
||||
- ✅ `sent_to_all` изменен с `default=False` на `default=True`
|
||||
- ✅ Теперь соответствует HTML форме (checkbox checked по умолчанию)
|
||||
|
||||
---
|
||||
|
||||
## ✅ **API Serializer**
|
||||
|
||||
### DocumentWriteSerializer (`api/v1/documents/serializers.py`):
|
||||
```python
|
||||
class DocumentWriteSerializer(serializers.ModelSerializer):
|
||||
recipient_ids = RecipientIDsField(write_only=True, required=False)
|
||||
department_ids = RecipientIDsField(write_only=True, required=False)
|
||||
|
||||
class Meta:
|
||||
fields = (
|
||||
"id", "title", "description", "file",
|
||||
"sent_to_all", "department_ids", "recipient_ids"
|
||||
)
|
||||
```
|
||||
|
||||
### Валидация:
|
||||
```python
|
||||
def validate(self, attrs):
|
||||
# При создании file обязателен
|
||||
if self.instance is None and not attrs.get("file"):
|
||||
raise ValidationError({"file": "Обязательное поле."})
|
||||
|
||||
# При sent_to_all=False требуются получатели ИЛИ отделы
|
||||
if not attrs.get("sent_to_all", True):
|
||||
if not attrs.get("recipient_ids") and not attrs.get("department_ids"):
|
||||
raise ValidationError({
|
||||
"recipient_ids": "Укажите получателей, отделы или установите sent_to_all=true."
|
||||
})
|
||||
return attrs
|
||||
```
|
||||
|
||||
### Поддерживаемые форматы для recipient_ids и department_ids:
|
||||
- ✅ Repeat-params: `?recipient_ids=1&recipient_ids=2&recipient_ids=3`
|
||||
- ✅ JSON: `recipient_ids: "[1,2,3]"`
|
||||
- ✅ CSV: `recipient_ids: "1,2,3"`
|
||||
|
||||
---
|
||||
|
||||
## ✅ **HTML Модальное окно**
|
||||
|
||||
### Форма создания (`templates/documents/document_list.html`):
|
||||
```html
|
||||
<form id="docCreateForm" enctype="multipart/form-data">
|
||||
<!-- Заголовок -->
|
||||
<input type="text" name="title" required maxlength="255">
|
||||
|
||||
<!-- Описание -->
|
||||
<textarea name="description" maxlength="2000"></textarea>
|
||||
|
||||
<!-- Файл -->
|
||||
<input type="file" name="file" required>
|
||||
|
||||
<!-- Отправить всем -->
|
||||
<input type="checkbox" name="sent_to_all" id="createSentToAll" checked>
|
||||
|
||||
<!-- Отделы (скрыт по умолчанию) -->
|
||||
<select name="department_ids" id="createDepartments" multiple hidden>
|
||||
|
||||
<!-- Получатели (скрыт по умолчанию) -->
|
||||
<div id="createRecipients" class="recipient-picker" hidden>
|
||||
</form>
|
||||
```
|
||||
|
||||
### Логика переключения:
|
||||
```javascript
|
||||
// При checked=true (отправить всем):
|
||||
// - Скрыть блок отделов
|
||||
// - Скрыть блок получателей
|
||||
|
||||
// При checked=false (выборочная отправка):
|
||||
// - Показать блок отделов
|
||||
// - Показать блок получателей
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ **JavaScript обработка**
|
||||
|
||||
### Создание документа (`static/js/components/documentCrudHandler.js`):
|
||||
```javascript
|
||||
async function handleCreate(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const formData = new FormData(createForm);
|
||||
|
||||
// Установка sent_to_all
|
||||
formData.set('sent_to_all', createAllCheckbox.checked ? 'true' : 'false');
|
||||
|
||||
if (!createAllCheckbox.checked) {
|
||||
// Сбор отделов
|
||||
const deptIds = Array.from(createDeptSelect.selectedOptions)
|
||||
.map(opt => opt.value);
|
||||
|
||||
// Сбор получателей
|
||||
const recipientIds = createPicker.getIds();
|
||||
|
||||
// Валидация
|
||||
if (deptIds.length === 0 && recipientIds.length === 0) {
|
||||
alert('Необходимо выбрать получателей или отделы');
|
||||
return;
|
||||
}
|
||||
|
||||
// Добавление в FormData (repeat-params)
|
||||
deptIds.forEach(id => formData.append('department_ids', id));
|
||||
recipientIds.forEach(id => formData.append('recipient_ids', id));
|
||||
} else {
|
||||
// При sent_to_all=true удаляем получателей
|
||||
formData.delete('recipient_ids');
|
||||
formData.delete('department_ids');
|
||||
}
|
||||
|
||||
// Отправка
|
||||
await fetch(apiListUrl, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: formData
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 **Сводная таблица соответствия**
|
||||
|
||||
| Поле | Модель | API | HTML | JavaScript | Статус |
|
||||
|------|--------|-----|------|------------|--------|
|
||||
| **title** | CharField(255, required) | required | required | ✅ | ✅ OK |
|
||||
| **description** | TextField(blank=True) | optional | optional | ✅ | ✅ OK |
|
||||
| **file** | FileField(required) | required (create) | required | ✅ | ✅ OK |
|
||||
| **sent_to_all** | Boolean(default=True) | Boolean | checkbox checked | 'true'/'false' | ✅ OK |
|
||||
| **departments** | ManyToMany(blank=True) | department_ids[] | select multiple | append() | ✅ OK |
|
||||
| **recipients** | ManyToMany(blank=True) | recipient_ids[] | RecipientPicker | append() | ✅ OK |
|
||||
| **uploaded_by** | ForeignKey(auto) | auto (request.user) | - | - | ✅ OK |
|
||||
| **uploaded_at** | DateTime(auto) | auto | - | - | ✅ OK |
|
||||
|
||||
---
|
||||
|
||||
## ✅ **Проверка всех возможностей модели**
|
||||
|
||||
### 1. ✅ **Создание документа для всех**
|
||||
```javascript
|
||||
{
|
||||
title: "Приказ",
|
||||
description: "Общий приказ",
|
||||
file: <File>,
|
||||
sent_to_all: true,
|
||||
department_ids: [], // Игнорируется
|
||||
recipient_ids: [] // Игнорируется
|
||||
}
|
||||
```
|
||||
**Результат**: Документ доступен всем активным сотрудникам.
|
||||
|
||||
---
|
||||
|
||||
### 2. ✅ **Создание документа для отделов**
|
||||
```javascript
|
||||
{
|
||||
title: "Инструкция",
|
||||
description: "Для IT отдела",
|
||||
file: <File>,
|
||||
sent_to_all: false,
|
||||
department_ids: [1, 2, 3], // ID отделов
|
||||
recipient_ids: []
|
||||
}
|
||||
```
|
||||
**Результат**: Документ доступен всем сотрудникам отделов 1, 2, 3.
|
||||
|
||||
---
|
||||
|
||||
### 3. ✅ **Создание документа для конкретных сотрудников**
|
||||
```javascript
|
||||
{
|
||||
title: "Личное",
|
||||
description: "Для Иванова и Петрова",
|
||||
file: <File>,
|
||||
sent_to_all: false,
|
||||
department_ids: [],
|
||||
recipient_ids: [10, 20] // ID сотрудников
|
||||
}
|
||||
```
|
||||
**Результат**: Документ доступен только сотрудникам с ID 10 и 20.
|
||||
|
||||
---
|
||||
|
||||
### 4. ✅ **Комбинация отделов и сотрудников**
|
||||
```javascript
|
||||
{
|
||||
title: "Важное",
|
||||
description: "Для IT + руководители",
|
||||
file: <File>,
|
||||
sent_to_all: false,
|
||||
department_ids: [1], // IT отдел
|
||||
recipient_ids: [30, 40] // + 2 руководителя
|
||||
}
|
||||
```
|
||||
**Результат**: Документ доступен всем из IT отдела + двум указанным сотрудникам.
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ **Исправленные проблемы**
|
||||
|
||||
### 1. ✅ **Несоответствие default значения**
|
||||
|
||||
**Было:**
|
||||
```python
|
||||
# Модель
|
||||
sent_to_all = models.BooleanField(default=False) # ❌
|
||||
|
||||
# HTML
|
||||
<input type="checkbox" checked> # ✅
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```python
|
||||
# Модель
|
||||
sent_to_all = models.BooleanField(default=True) # ✅
|
||||
|
||||
# HTML
|
||||
<input type="checkbox" checked> # ✅
|
||||
```
|
||||
|
||||
**Миграция**: `0003_change_sent_to_all_default.py`
|
||||
|
||||
---
|
||||
|
||||
### 2. ✅ **Улучшена обработка ошибок**
|
||||
|
||||
**Было:**
|
||||
```javascript
|
||||
catch (error) {
|
||||
alert('Не удалось создать документ: ' + error.message);
|
||||
}
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```javascript
|
||||
catch (error) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
const errorMsg = errorData.detail
|
||||
|| errorData.non_field_errors?.[0]
|
||||
|| Object.values(errorData).flat().join(', ')
|
||||
|| 'HTTP ' + response.status;
|
||||
|
||||
console.error('Ошибка создания документа:', error);
|
||||
alert('Не удалось создать документ:\n' + errorMsg);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. ✅ **Улучшено сообщение валидации**
|
||||
|
||||
**Было:**
|
||||
```javascript
|
||||
alert('Выберите отделы, получателей или включите «Отправить всем».');
|
||||
```
|
||||
|
||||
**Стало:**
|
||||
```javascript
|
||||
alert('Необходимо выбрать:\n- Хотя бы один отдел, или\n- Хотя бы одного получателя, или\n- Включить «Отправить всем»');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 **Тестовые сценарии**
|
||||
|
||||
### Сценарий 1: Создание документа для всех ✅
|
||||
1. Открыть модальное окно создания
|
||||
2. Заполнить заголовок: "Общий документ"
|
||||
3. Прикрепить файл
|
||||
4. Убедиться что "Отправить всем" включено (по умолчанию)
|
||||
5. Нажать "Сохранить"
|
||||
|
||||
**Ожидаемый результат**: Документ создан, доступен всем.
|
||||
|
||||
---
|
||||
|
||||
### Сценарий 2: Создание документа для отдела ✅
|
||||
1. Открыть модальное окно создания
|
||||
2. Заполнить заголовок: "Для IT"
|
||||
3. Прикрепить файл
|
||||
4. Отключить "Отправить всем"
|
||||
5. Выбрать отдел "IT"
|
||||
6. Нажать "Сохранить"
|
||||
|
||||
**Ожидаемый результат**: Документ создан, доступен только IT отделу.
|
||||
|
||||
---
|
||||
|
||||
### Сценарий 3: Создание документа для сотрудников ✅
|
||||
1. Открыть модальное окно создания
|
||||
2. Заполнить заголовок: "Личное"
|
||||
3. Прикрепить файл
|
||||
4. Отключить "Отправить всем"
|
||||
5. Найти и выбрать сотрудников через RecipientPicker
|
||||
6. Нажать "Сохранить"
|
||||
|
||||
**Ожидаемый результат**: Документ создан, доступен только выбранным сотрудникам.
|
||||
|
||||
---
|
||||
|
||||
### Сценарий 4: Валидация - отключен "Отправить всем", не выбраны получатели ❌→✅
|
||||
1. Открыть модальное окно создания
|
||||
2. Заполнить заголовок и прикрепить файл
|
||||
3. Отключить "Отправить всем"
|
||||
4. НЕ выбирать отделы и сотрудников
|
||||
5. Нажать "Сохранить"
|
||||
|
||||
**Ожидаемый результат**:
|
||||
- ✅ Клиентская валидация: Alert с подсказкой
|
||||
- ✅ Серверная валидация: 400 Bad Request
|
||||
|
||||
---
|
||||
|
||||
### Сценарий 5: Валидация - не прикреплен файл ❌→✅
|
||||
1. Открыть модальное окно создания
|
||||
2. Заполнить заголовок
|
||||
3. НЕ прикреплять файл
|
||||
4. Нажать "Сохранить"
|
||||
|
||||
**Ожидаемый результат**:
|
||||
- ✅ HTML5 валидация: "Пожалуйста, выберите файл"
|
||||
- ✅ Кнопка "Сохранить" не активна до выбора файла
|
||||
|
||||
---
|
||||
|
||||
## 📋 **Чеклист функциональности**
|
||||
|
||||
### Основные возможности:
|
||||
- ✅ Создание документа с заголовком
|
||||
- ✅ Создание документа с описанием (необязательно)
|
||||
- ✅ Загрузка файла (обязательно)
|
||||
- ✅ Отправка всем сотрудникам (sent_to_all=true)
|
||||
- ✅ Отправка конкретным отделам (department_ids)
|
||||
- ✅ Отправка конкретным сотрудникам (recipient_ids)
|
||||
- ✅ Комбинация отделов и сотрудников
|
||||
- ✅ Автоматическая установка uploaded_by
|
||||
- ✅ Автоматическая установка uploaded_at
|
||||
|
||||
### Валидация:
|
||||
- ✅ Заголовок обязателен
|
||||
- ✅ Файл обязателен при создании
|
||||
- ✅ При sent_to_all=false требуются получатели ИЛИ отделы
|
||||
- ✅ Клиентская валидация (JavaScript)
|
||||
- ✅ Серверная валидация (DRF)
|
||||
- ✅ Читаемые сообщения об ошибках
|
||||
|
||||
### UI/UX:
|
||||
- ✅ Переключение видимости блоков (sent_to_all)
|
||||
- ✅ RecipientPicker для выбора сотрудников
|
||||
- ✅ Select multiple для выбора отделов
|
||||
- ✅ Загрузка списка отделов из API
|
||||
- ✅ Обработка ошибок с alert
|
||||
- ✅ Перезагрузка страницы после создания
|
||||
|
||||
### Интеграция:
|
||||
- ✅ Multipart/form-data для файла
|
||||
- ✅ Repeat-params для массивов ID
|
||||
- ✅ Authorization header
|
||||
- ✅ CSRF token
|
||||
- ✅ API v1/documents endpoint
|
||||
|
||||
---
|
||||
|
||||
## 🎯 **Итоговая оценка**
|
||||
|
||||
### Соответствие модели: ✅ 100%
|
||||
Все поля модели представлены в форме и корректно обрабатываются.
|
||||
|
||||
### Соответствие API: ✅ 100%
|
||||
Все поля API сериализатора поддержаны в форме.
|
||||
|
||||
### Валидация: ✅ 95%
|
||||
- ✅ Клиентская валидация работает
|
||||
- ✅ Серверная валидация работает
|
||||
- ⚠️ Можно добавить валидацию размера файла на клиенте
|
||||
|
||||
### UX: ✅ 90%
|
||||
- ✅ Интуитивно понятный интерфейс
|
||||
- ✅ Переключение блоков работает плавно
|
||||
- ⚠️ Можно добавить индикатор загрузки (spinner)
|
||||
|
||||
### Обработка ошибок: ✅ 85%
|
||||
- ✅ Основные ошибки обрабатываются
|
||||
- ✅ Показываются читаемые сообщения
|
||||
- ⚠️ Можно улучшить форматирование ошибок
|
||||
|
||||
---
|
||||
|
||||
## 🚀 **Рекомендации для улучшения**
|
||||
|
||||
### 1. Добавить индикатор загрузки
|
||||
```javascript
|
||||
const submitBtn = createForm.querySelector('button[type="submit"]');
|
||||
submitBtn.disabled = true;
|
||||
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Сохранение...';
|
||||
|
||||
// После ответа
|
||||
submitBtn.disabled = false;
|
||||
submitBtn.innerHTML = '<i class="bi-check-lg"></i> Сохранить';
|
||||
```
|
||||
|
||||
### 2. Добавить валидацию размера файла
|
||||
```javascript
|
||||
const fileInput = createForm.querySelector('input[type="file"]');
|
||||
fileInput.addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
const maxSize = 50 * 1024 * 1024; // 50 MB
|
||||
|
||||
if (file && file.size > maxSize) {
|
||||
alert('Размер файла не должен превышать 50 МБ');
|
||||
fileInput.value = '';
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Добавить preview загруженного файла
|
||||
```javascript
|
||||
<div class="file-preview" style="display:none;">
|
||||
<i class="bi-file-earmark"></i>
|
||||
<span class="filename"></span>
|
||||
<button type="button" class="btn-close"></button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 4. Улучшить отображение ошибок
|
||||
```javascript
|
||||
// Вместо alert использовать toast
|
||||
function showError(message) {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'toast align-items-center text-bg-danger';
|
||||
toast.innerHTML = `
|
||||
<div class="d-flex">
|
||||
<div class="toast-body">${message}</div>
|
||||
<button type="button" class="btn-close me-2 m-auto"></button>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(toast);
|
||||
new bootstrap.Toast(toast).show();
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ **Заключение**
|
||||
|
||||
Модальное окно создания документов **полностью реализует все возможности модели и API**.
|
||||
|
||||
### Что работает отлично:
|
||||
- ✅ Все поля модели представлены
|
||||
- ✅ Все сценарии использования поддержаны
|
||||
- ✅ Валидация работает на клиенте и сервере
|
||||
- ✅ Обработка ошибок корректна
|
||||
- ✅ UI интуитивно понятен
|
||||
|
||||
### Что было исправлено:
|
||||
- ✅ Несоответствие default значения sent_to_all
|
||||
- ✅ Улучшена обработка ошибок API
|
||||
- ✅ Улучшены сообщения валидации
|
||||
|
||||
### Что можно улучшить (опционально):
|
||||
- ⚠️ Индикатор загрузки
|
||||
- ⚠️ Валидация размера файла на клиенте
|
||||
- ⚠️ Preview файла
|
||||
- ⚠️ Toast уведомления вместо alert
|
||||
|
||||
**Модальное окно готово к использованию!** ✅
|
||||
@ -1,487 +0,0 @@
|
||||
# Анализ обработки редактирования сообщений через WebSocket
|
||||
|
||||
## Текущая реализация
|
||||
|
||||
### Backend (edit_message view)
|
||||
|
||||
**Что делает:**
|
||||
```python
|
||||
def edit_message(request, message_id):
|
||||
# 1. Получает новый текст из request
|
||||
new_content = data.get('content', '').strip()
|
||||
|
||||
# 2. Находит сообщение
|
||||
message = Message.objects.get(pk=message_id, author=request.user)
|
||||
|
||||
# 3. Сохраняет историю
|
||||
message.edit_history.append({
|
||||
'timestamp': timezone.now().isoformat(),
|
||||
'old_content': message.content
|
||||
})
|
||||
|
||||
# 4. Обновляет ТОЛЬКО текст
|
||||
message.content = new_content
|
||||
message.is_edited = True
|
||||
message.edited_at = timezone.now()
|
||||
message.save()
|
||||
|
||||
# 5. Отправляет через WebSocket
|
||||
payload = serialize_message(message) # ← ПОЛНАЯ сериализация!
|
||||
```
|
||||
|
||||
**Что включает `serialize_message`:**
|
||||
- ✅ Базовая информация (id, content, author)
|
||||
- ✅ **Вложения** (`attachments`) - полный список
|
||||
- ✅ **Ответ на сообщение** (`reply_to`) - вся информация
|
||||
- ✅ **Реакции** (`reactions_summary`)
|
||||
- ✅ **Голосования** (`poll`)
|
||||
- ✅ **Информация о пересылке** (`forwarded_from`)
|
||||
- ✅ Флаги (is_edited, is_deleted, is_pinned, etc.)
|
||||
|
||||
### Frontend (messageEditing.js)
|
||||
|
||||
**Что делает:**
|
||||
```javascript
|
||||
function updateMessageInDOM(message) {
|
||||
// ❌ ПРОБЛЕМА: Обновляет ТОЛЬКО текст!
|
||||
const bubbleEl = messageEl.querySelector('.chat-bubble__text, .message-bubble__text');
|
||||
if (bubbleEl) {
|
||||
bubbleEl.textContent = message.content; // ← ТОЛЬКО ТЕКСТ!
|
||||
}
|
||||
|
||||
// ✅ Добавляет индикатор "(изменено)"
|
||||
if (message.is_edited) {
|
||||
// ... создает editedIndicator
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔴 ПРОБЛЕМЫ
|
||||
|
||||
### 1. Вложения НЕ обновляются
|
||||
**Что происходит:**
|
||||
- Backend отправляет полный `message.attachments[]` через WebSocket
|
||||
- Frontend игнорирует это поле
|
||||
- Если при редактировании удалили/добавили файлы - DOM не обновится
|
||||
|
||||
**Пример сценария:**
|
||||
```
|
||||
1. Сообщение: "Смотри фото" + [image1.jpg, image2.jpg]
|
||||
2. Редактирование: "Смотри фото" (удалили image2.jpg)
|
||||
3. WebSocket отправляет: { content: "...", attachments: [{image1.jpg}] }
|
||||
4. Frontend обновляет: ❌ Текст изменен, но image2.jpg всё ещё отображается!
|
||||
```
|
||||
|
||||
### 2. Reply-to НЕ обновляется
|
||||
**Что происходит:**
|
||||
- Backend отправляет `message.reply_to` (может быть null если ответ отменили)
|
||||
- Frontend игнорирует это поле
|
||||
- Если при редактировании отменили ответ - блок reply всё ещё показывается
|
||||
|
||||
**Пример сценария:**
|
||||
```
|
||||
1. Сообщение: [reply-to: msg#123] "Согласен"
|
||||
2. Редактирование: убрали ответ, текст "Согласен"
|
||||
3. WebSocket: { content: "Согласен", reply_to: null }
|
||||
4. Frontend: ❌ Блок reply_to всё ещё показывается!
|
||||
```
|
||||
|
||||
### 3. Голосования НЕ обновляются
|
||||
**Что происходит:**
|
||||
- Backend отправляет полные данные `message.poll`
|
||||
- Frontend игнорирует poll
|
||||
- Если отредактировали вопрос или опции - не обновится
|
||||
|
||||
### 4. Реакции МОГУТ потеряться
|
||||
**Что происходит:**
|
||||
- Backend отправляет `reactions_summary`
|
||||
- Frontend НЕ обновляет реакции при редактировании
|
||||
- Если между редактированием кто-то добавил реакцию - может потеряться
|
||||
|
||||
## ⚙️ Что МОЖНО при редактировании
|
||||
|
||||
### Backend не поддерживает:
|
||||
|
||||
**НЕТ обработки вложений:**
|
||||
```python
|
||||
# edit_message принимает ТОЛЬКО content
|
||||
new_content = data.get('content', '').strip()
|
||||
# ❌ Нет data.get('attachments')
|
||||
# ❌ Нет data.get('remove_attachments')
|
||||
# ❌ Нет обработки новых файлов
|
||||
```
|
||||
|
||||
**НЕТ обработки reply_to:**
|
||||
```python
|
||||
# ❌ Нет data.get('reply_to_id')
|
||||
# ❌ Нельзя отменить ответ
|
||||
# ❌ Нельзя изменить на какое сообщение отвечаем
|
||||
```
|
||||
|
||||
**НЕТ обработки poll:**
|
||||
```python
|
||||
# ❌ Нельзя изменить вопрос голосования
|
||||
# ❌ Нельзя добавить/удалить опции
|
||||
```
|
||||
|
||||
### Вывод: В текущей реализации при редактировании можно менять ТОЛЬКО текст!
|
||||
|
||||
## 🔧 РЕКОМЕНДАЦИИ
|
||||
|
||||
### Краткосрочные (исправить баги)
|
||||
|
||||
#### 1. Обновить `messageEditing.js` для полной перерисовки
|
||||
|
||||
**Проблема:** Частичное обновление DOM ненадёжно
|
||||
|
||||
**Решение:** Использовать `messageRenderer.js` для полной перерисовки
|
||||
|
||||
```javascript
|
||||
// messageEditing.js
|
||||
import messageRenderer from './messageRenderer.js';
|
||||
|
||||
function updateMessageInDOM(message) {
|
||||
const messageElements = document.querySelectorAll(
|
||||
`[data-message-id="${message.id}"]`
|
||||
);
|
||||
|
||||
if (messageElements.length === 0) return;
|
||||
|
||||
messageElements.forEach(oldMessageEl => {
|
||||
// ПОЛНАЯ перерисовка сообщения
|
||||
const newMessageHTML = messageRenderer.renderMessage(message, {
|
||||
currentUserId: window.meId,
|
||||
chatId: window.chatId
|
||||
});
|
||||
|
||||
// Заменяем HTML целиком
|
||||
const tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = newMessageHTML;
|
||||
const newMessageEl = tempDiv.firstElementChild;
|
||||
|
||||
// Сохраняем скролл позицию
|
||||
const scrollContainer = document.getElementById('chatScroll');
|
||||
const scrollBefore = scrollContainer.scrollTop;
|
||||
|
||||
// Заменяем элемент
|
||||
oldMessageEl.replaceWith(newMessageEl);
|
||||
|
||||
// Восстанавливаем скролл
|
||||
scrollContainer.scrollTop = scrollBefore;
|
||||
|
||||
// Переинициализируем компоненты
|
||||
reinitMessageComponents(newMessageEl, message.id);
|
||||
});
|
||||
}
|
||||
|
||||
function reinitMessageComponents(messageEl, messageId) {
|
||||
// Реакции
|
||||
if (window.reactions) {
|
||||
window.reactions.initMessageReactions(
|
||||
messageEl,
|
||||
messageId,
|
||||
window.meId
|
||||
);
|
||||
}
|
||||
|
||||
// Контекстное меню (уже работает через делегирование)
|
||||
// Attachments (если есть lightbox и т.д.)
|
||||
}
|
||||
```
|
||||
|
||||
**Преимущества:**
|
||||
- ✅ Вложения обновятся автоматически
|
||||
- ✅ Reply-to обновится автоматически
|
||||
- ✅ Poll обновится автоматически
|
||||
- ✅ Все стили и классы применятся правильно
|
||||
- ✅ Не нужно дублировать логику рендеринга
|
||||
|
||||
**Недостатки:**
|
||||
- ⚠️ Требует проверки что `messageRenderer.renderMessage()` экспортирован
|
||||
- ⚠️ Нужно убедиться что все компоненты переинициализируются
|
||||
|
||||
#### 2. Альтернатива: Умное частичное обновление
|
||||
|
||||
Если полная перерисовка слишком сложна:
|
||||
|
||||
```javascript
|
||||
function updateMessageInDOM(message) {
|
||||
const messageEl = document.querySelector(`[data-message-id="${message.id}"]`);
|
||||
if (!messageEl) return;
|
||||
|
||||
// 1. Обновляем текст
|
||||
updateTextContent(messageEl, message);
|
||||
|
||||
// 2. Обновляем вложения
|
||||
if (message.has_attachments) {
|
||||
updateAttachments(messageEl, message.attachments);
|
||||
} else {
|
||||
removeAllAttachments(messageEl);
|
||||
}
|
||||
|
||||
// 3. Обновляем reply-to
|
||||
if (message.reply_to) {
|
||||
updateReplyBlock(messageEl, message.reply_to);
|
||||
} else {
|
||||
removeReplyBlock(messageEl);
|
||||
}
|
||||
|
||||
// 4. Обновляем poll
|
||||
if (message.poll) {
|
||||
updatePollBlock(messageEl, message.poll);
|
||||
}
|
||||
|
||||
// 5. Обновляем индикатор редактирования
|
||||
updateEditedIndicator(messageEl, message.is_edited);
|
||||
}
|
||||
|
||||
function updateAttachments(messageEl, attachments) {
|
||||
let attachmentsContainer = messageEl.querySelector('.message-attachments');
|
||||
|
||||
if (!attachmentsContainer) {
|
||||
// Создаём контейнер если его нет
|
||||
attachmentsContainer = document.createElement('div');
|
||||
attachmentsContainer.className = 'message-attachments mt-2';
|
||||
messageEl.querySelector('.message-bubble').appendChild(attachmentsContainer);
|
||||
}
|
||||
|
||||
// Очищаем и рендерим заново
|
||||
attachmentsContainer.innerHTML = '';
|
||||
attachments.forEach(att => {
|
||||
const attEl = renderAttachment(att);
|
||||
attachmentsContainer.appendChild(attEl);
|
||||
});
|
||||
}
|
||||
|
||||
function removeAllAttachments(messageEl) {
|
||||
const container = messageEl.querySelector('.message-attachments');
|
||||
if (container) {
|
||||
container.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function updateReplyBlock(messageEl, replyData) {
|
||||
let replyBlock = messageEl.querySelector('.message-reply-to');
|
||||
|
||||
if (!replyBlock) {
|
||||
// Создаём блок если его нет
|
||||
replyBlock = document.createElement('div');
|
||||
replyBlock.className = 'message-reply-to';
|
||||
const bubble = messageEl.querySelector('.message-bubble');
|
||||
bubble.insertBefore(replyBlock, bubble.firstChild);
|
||||
}
|
||||
|
||||
replyBlock.innerHTML = `
|
||||
<div class="reply-preview">
|
||||
<strong>${replyData.author_name}</strong>
|
||||
<p class="text-muted">${replyData.content}</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function removeReplyBlock(messageEl) {
|
||||
const replyBlock = messageEl.querySelector('.message-reply-to');
|
||||
if (replyBlock) {
|
||||
replyBlock.remove();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Среднесрочные (функциональность)
|
||||
|
||||
#### 3. Расширить API `edit_message` для поддержки вложений
|
||||
|
||||
```python
|
||||
@csrf_protect
|
||||
@login_required
|
||||
@require_POST
|
||||
def edit_message(request, message_id):
|
||||
"""Редактирование сообщения с поддержкой вложений и reply_to"""
|
||||
import json
|
||||
|
||||
try:
|
||||
data = json.loads(request.body)
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({'ok': False, 'error': 'Invalid JSON'}, status=400)
|
||||
|
||||
message = get_object_or_404(Message, pk=message_id, author=request.user)
|
||||
|
||||
# Обновляем контент
|
||||
new_content = data.get('content', '').strip()
|
||||
if new_content:
|
||||
message.content = new_content
|
||||
|
||||
# НОВОЕ: Обработка reply_to
|
||||
if 'reply_to_id' in data:
|
||||
reply_to_id = data.get('reply_to_id')
|
||||
if reply_to_id is None:
|
||||
# Отменяем ответ
|
||||
message.reply_to = None
|
||||
elif reply_to_id:
|
||||
# Проверяем существование сообщения
|
||||
try:
|
||||
reply_msg = Message.objects.get(
|
||||
pk=reply_to_id,
|
||||
chat=message.chat
|
||||
)
|
||||
message.reply_to = reply_msg
|
||||
except Message.DoesNotExist:
|
||||
return JsonResponse({
|
||||
'ok': False,
|
||||
'error': 'Reply message not found'
|
||||
}, status=400)
|
||||
|
||||
# НОВОЕ: Обработка вложений
|
||||
if 'remove_attachments' in data:
|
||||
# Удаляем указанные вложения
|
||||
remove_ids = data.get('remove_attachments', [])
|
||||
MessageAttachment.objects.filter(
|
||||
message=message,
|
||||
id__in=remove_ids
|
||||
).delete()
|
||||
|
||||
# Обновляем флаг
|
||||
message.has_attachments = message.attachments.exists()
|
||||
|
||||
# Сохраняем историю
|
||||
message.edit_history.append({
|
||||
'timestamp': timezone.now().isoformat(),
|
||||
'old_content': message.content,
|
||||
'changes': {
|
||||
'content': 'content' in data,
|
||||
'reply_to': 'reply_to_id' in data,
|
||||
'attachments': 'remove_attachments' in data
|
||||
}
|
||||
})
|
||||
|
||||
message.is_edited = True
|
||||
message.edited_at = timezone.now()
|
||||
message.save()
|
||||
|
||||
# Отправляем обновление через WebSocket
|
||||
channel_layer = get_channel_layer()
|
||||
payload = serialize_message(message)
|
||||
|
||||
async_to_sync(channel_layer.group_send)(
|
||||
f'chat_{message.chat_id}',
|
||||
{
|
||||
'type': 'chat.message_edited',
|
||||
'chat_id': message.chat_id,
|
||||
'payload': payload
|
||||
}
|
||||
)
|
||||
|
||||
return JsonResponse({'ok': True, 'message': payload})
|
||||
```
|
||||
|
||||
#### 4. Обновить UI редактирования
|
||||
|
||||
```javascript
|
||||
// В messageContextMenu.js или где у вас форма редактирования
|
||||
async function submitEditedMessage(messageId, newContent, options = {}) {
|
||||
const payload = {
|
||||
content: newContent
|
||||
};
|
||||
|
||||
// НОВОЕ: Поддержка отмены ответа
|
||||
if (options.removeReply) {
|
||||
payload.reply_to_id = null;
|
||||
}
|
||||
|
||||
// НОВОЕ: Поддержка удаления вложений
|
||||
if (options.removeAttachments && options.removeAttachments.length) {
|
||||
payload.remove_attachments = options.removeAttachments;
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
`/api/v1/communications/messages/${messageId}/edit/`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': getCookie('csrftoken')
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
}
|
||||
);
|
||||
|
||||
return response.json();
|
||||
}
|
||||
```
|
||||
|
||||
### Долгосрочные (архитектура)
|
||||
|
||||
#### 5. Использовать React/Vue для реактивного рендеринга
|
||||
|
||||
Текущий подход (императивный DOM) сложен в поддержке:
|
||||
- Нужно вручную синхронизировать все изменения
|
||||
- Легко упустить какое-то поле
|
||||
- Дублирование логики рендеринга
|
||||
|
||||
**React пример:**
|
||||
```jsx
|
||||
function Message({ message, currentUserId }) {
|
||||
return (
|
||||
<div data-message-id={message.id}>
|
||||
{message.reply_to && (
|
||||
<ReplyBlock reply={message.reply_to} />
|
||||
)}
|
||||
|
||||
<MessageBubble
|
||||
content={message.content}
|
||||
author={message.author_name}
|
||||
isEdited={message.is_edited}
|
||||
/>
|
||||
|
||||
{message.attachments && (
|
||||
<Attachments files={message.attachments} />
|
||||
)}
|
||||
|
||||
{message.poll && (
|
||||
<Poll data={message.poll} />
|
||||
)}
|
||||
|
||||
<Reactions
|
||||
summary={message.reactions_summary}
|
||||
currentUserId={currentUserId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// При получении WebSocket события:
|
||||
function handleMessageEdited(event) {
|
||||
const updatedMessage = event.detail.payload;
|
||||
|
||||
// React автоматически перерисует только изменённые части!
|
||||
setState(prevState => ({
|
||||
...prevState,
|
||||
messages: prevState.messages.map(m =>
|
||||
m.id === updatedMessage.id ? updatedMessage : m
|
||||
)
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
## 📋 ИТОГО
|
||||
|
||||
### Текущее состояние:
|
||||
- ❌ Backend отправляет полные данные, но Frontend обновляет только текст
|
||||
- ❌ Вложения не обновляются
|
||||
- ❌ Reply-to не обновляется
|
||||
- ❌ Poll не обновляется
|
||||
- ❌ Backend не позволяет редактировать что-либо кроме текста
|
||||
|
||||
### Минимальный фикс (1-2 часа):
|
||||
1. Использовать `messageRenderer.renderMessage()` для полной перерисовки
|
||||
2. Добавить переинициализацию компонентов
|
||||
|
||||
### Полный фикс (1-2 дня):
|
||||
1. Расширить `edit_message` API для поддержки вложений/reply_to
|
||||
2. Обновить UI формы редактирования
|
||||
3. Тестирование всех сценариев
|
||||
|
||||
### Идеальное решение (1-2 недели):
|
||||
1. Мигрировать на React/Vue
|
||||
2. Централизованное state management
|
||||
3. Реактивное обновление UI
|
||||
@ -1,379 +0,0 @@
|
||||
# Анализ: Загрузка и встраивание опросов (polls) в DOM
|
||||
|
||||
## 🔍 Текущая архитектура
|
||||
|
||||
### Backend: Сериализация опросов
|
||||
|
||||
**Файл**: `backend/communications/consumers.py`
|
||||
|
||||
```python
|
||||
def serialize_message(m: Message) -> dict:
|
||||
# ...
|
||||
|
||||
# Голосование
|
||||
if hasattr(m, 'poll'):
|
||||
poll = m.poll
|
||||
poll_data = {
|
||||
"id": poll.id,
|
||||
"question": poll.question,
|
||||
"is_anonymous": poll.is_anonymous,
|
||||
"is_multiple_choice": poll.is_multiple_choice,
|
||||
"is_quiz": poll.is_quiz,
|
||||
"is_closed": poll.is_closed,
|
||||
"closes_at": poll.closes_at.isoformat() if poll.closes_at else None,
|
||||
"total_voters": poll.total_voters,
|
||||
"options": []
|
||||
}
|
||||
for option in poll.options.all():
|
||||
poll_data["options"].append({
|
||||
"id": option.id,
|
||||
"text": option.text,
|
||||
"position": option.position,
|
||||
"vote_count": option.vote_count,
|
||||
"percentage": 0 # Будет пересчитан на клиенте
|
||||
})
|
||||
data["poll"] = poll_data
|
||||
```
|
||||
|
||||
✅ **Опросы загружаются** в payload сообщения.
|
||||
|
||||
---
|
||||
|
||||
### Frontend: Генерация HTML
|
||||
|
||||
#### ❌ ПРОБЛЕМА: MessageRenderer НЕ генерировал HTML для опросов
|
||||
|
||||
**Файл**: `backend/static/js/components/messageRenderer.js`
|
||||
|
||||
**ДО исправления:**
|
||||
```javascript
|
||||
buildMessageHtml(msg, isOwn) {
|
||||
const attachmentsHtml = msg.attachments?.length ?
|
||||
msg.attachments.map(att => this.buildAttachmentHtml(att)).join('') : '';
|
||||
|
||||
// Опросы НЕ рендерились!
|
||||
|
||||
return `
|
||||
<div class="bubble">
|
||||
${msg.content}
|
||||
${attachmentsHtml}
|
||||
<!-- ← poll отсутствует! -->
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
**ПОСЛЕ исправления:**
|
||||
```javascript
|
||||
buildMessageHtml(msg, isOwn) {
|
||||
const attachmentsHtml = msg.attachments?.length ?
|
||||
msg.attachments.map(att => this.buildAttachmentHtml(att)).join('') : '';
|
||||
|
||||
// ✅ Добавлена генерация HTML для опроса
|
||||
const pollHtml = msg.poll ? this.buildPollHtml(msg.poll) : '';
|
||||
|
||||
return `
|
||||
<div class="bubble">
|
||||
${msg.content}
|
||||
${attachmentsHtml}
|
||||
${pollHtml} ← добавлено!
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// ✅ Новый метод
|
||||
buildPollHtml(poll) {
|
||||
if (!poll || !poll.id) return '';
|
||||
|
||||
const totalVotes = poll.total_voters || 0;
|
||||
const isClosed = poll.is_closed || false;
|
||||
|
||||
return `
|
||||
<div class="poll-widget mt-2" data-poll-id="${poll.id}">
|
||||
<div class="poll-question mb-3">
|
||||
<strong>${this.escapeHtml(poll.question)}</strong>
|
||||
</div>
|
||||
<div class="poll-options">
|
||||
${poll.options?.map(option => `
|
||||
<div class="poll-option mb-2" data-option-id="${option.id}">
|
||||
<button type="button" class="btn btn-outline-secondary btn-poll-option w-100 text-start"
|
||||
data-option-id="${option.id}">
|
||||
${this.escapeHtml(option.text)}
|
||||
</button>
|
||||
</div>
|
||||
`).join('') || ''}
|
||||
</div>
|
||||
<div class="poll-footer mt-3">
|
||||
<div class="small text-muted">
|
||||
${totalVotes} проголосовало
|
||||
${poll.is_anonymous ? ' • Анонимное' : ''}
|
||||
${poll.is_multiple_choice ? ' • Множественный выбор' : ''}
|
||||
${isClosed ? ' • Закрыто' : ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Frontend: Инициализация опросов
|
||||
|
||||
**Файл**: `backend/static/js/components/chatPoll.js`
|
||||
|
||||
```javascript
|
||||
class ChatPoll {
|
||||
initializeExistingPolls() {
|
||||
const pollWidgets = document.querySelectorAll('.poll-widget[data-poll-id]');
|
||||
pollWidgets.forEach((widget) => {
|
||||
const pollId = widget.dataset.pollId;
|
||||
this.refreshPoll(pollId, widget);
|
||||
});
|
||||
}
|
||||
|
||||
async refreshPoll(pollId, pollWidget = null) {
|
||||
const widget = pollWidget || document.querySelector(`[data-poll-id="${pollId}"]`);
|
||||
if (!widget) return;
|
||||
|
||||
// Загружает актуальные данные с сервера
|
||||
const results = await this.fetchPollResults(pollId);
|
||||
if (!results) return;
|
||||
|
||||
// Обновляет HTML опроса (результаты, проценты и т.д.)
|
||||
this.updatePollWidget(widget, results);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
✅ `ChatPoll` обрабатывает существующие опросы через `initializeExistingPolls()`.
|
||||
|
||||
---
|
||||
|
||||
### Frontend: Реинициализация после редактирования
|
||||
|
||||
**Файл**: `backend/static/js/components/messageEditing.js`
|
||||
|
||||
**ДО исправления:**
|
||||
```javascript
|
||||
function reinitMessageComponents(messageEl, messageId, currentUserId) {
|
||||
// 4. Голосования (если есть)
|
||||
const pollEl = messageEl.querySelector('[data-poll-id]');
|
||||
if (pollEl && window.chatPoll) {
|
||||
const pollId = pollEl.dataset.pollId;
|
||||
// ❌ Только логирование, НЕ инициализация!
|
||||
console.log('[MessageEditing] ✓ Poll detected, id:', pollId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**ПОСЛЕ исправления:**
|
||||
```javascript
|
||||
function reinitMessageComponents(messageEl, messageId, currentUserId) {
|
||||
// 4. Голосования (если есть)
|
||||
const pollEl = messageEl.querySelector('[data-poll-id]');
|
||||
if (pollEl && window.chatPoll) {
|
||||
const pollId = pollEl.dataset.pollId;
|
||||
console.log('[MessageEditing] Refreshing poll:', pollId);
|
||||
// ✅ Явно вызываем refreshPoll для обновления состояния
|
||||
window.chatPoll.refreshPoll(pollId, pollEl);
|
||||
console.log('[MessageEditing] ✓ Poll reinitialized, id:', pollId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Процесс загрузки и встраивания
|
||||
|
||||
### 1. Первоначальная загрузка сообщений
|
||||
|
||||
```
|
||||
User opens chat
|
||||
↓
|
||||
Django renders chat_detail.html
|
||||
↓
|
||||
JavaScript загружает начальные сообщения через API
|
||||
↓
|
||||
Для каждого сообщения:
|
||||
├─ MessageRenderer.buildMessageHtml(msg) создаёт HTML
|
||||
│ ├─ msg.content → текст
|
||||
│ ├─ msg.attachments → вложения
|
||||
│ ├─ msg.reply_to → ответ на сообщение
|
||||
│ └─ msg.poll → ТЕПЕРЬ ГЕНЕРИРУЕТСЯ! ✅
|
||||
│ └─ buildPollHtml(poll) → <div class="poll-widget" data-poll-id="...">
|
||||
↓
|
||||
HTML вставляется в DOM
|
||||
↓
|
||||
ChatPoll.initializeExistingPolls() находит все [data-poll-id]
|
||||
└─ Для каждого: refreshPoll(pollId)
|
||||
└─ Загружает актуальные данные с сервера
|
||||
└─ Обновляет проценты, результаты
|
||||
```
|
||||
|
||||
### 2. Создание нового опроса через UI
|
||||
|
||||
```
|
||||
User clicks "Создать голосование"
|
||||
↓
|
||||
Modal открывается
|
||||
↓
|
||||
User заполняет вопрос, варианты ответов
|
||||
↓
|
||||
ChatPoll.submitPoll() → POST /api/v1/communications/polls/create/
|
||||
↓
|
||||
Backend создаёт Poll + PollOption
|
||||
↓
|
||||
WebSocket → new_message event (с poll в payload)
|
||||
↓
|
||||
userWebSocket.js → dispatchEvent('new_message')
|
||||
↓
|
||||
chatScroll добавляет новое сообщение в DOM
|
||||
↓
|
||||
ChatPoll.initializeExistingPolls() или refreshPoll()
|
||||
```
|
||||
|
||||
### 3. Редактирование сообщения с опросом
|
||||
|
||||
```
|
||||
User edits message with poll
|
||||
↓
|
||||
POST /api/v1/communications/messages/{id}/edit/
|
||||
↓
|
||||
Backend: message.save() + prefetch poll/poll__options
|
||||
↓
|
||||
serialize_message(message) → включает poll data
|
||||
↓
|
||||
WebSocket → message_edited event
|
||||
↓
|
||||
userWebSocket.js → dispatchEvent('chat:message-edited')
|
||||
↓
|
||||
messageEditing.js: handleMessageEdited
|
||||
├─ MessageRenderer.buildMessageHtml(message) → ТЕПЕРЬ включает poll! ✅
|
||||
│ └─ buildPollHtml(message.poll) → генерирует HTML
|
||||
├─ oldMessageEl.replaceWith(newMessageEl)
|
||||
└─ reinitMessageComponents(newMessageEl)
|
||||
└─ window.chatPoll.refreshPoll(pollId, pollEl) ✅
|
||||
└─ Обновляет состояние опроса
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📊 Структура данных опроса
|
||||
|
||||
### Backend (serialize_message)
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 495,
|
||||
"content": "Выберите вариант",
|
||||
"poll": {
|
||||
"id": 10,
|
||||
"question": "Какой язык программирования вы предпочитаете?",
|
||||
"is_anonymous": false,
|
||||
"is_multiple_choice": false,
|
||||
"is_quiz": false,
|
||||
"is_closed": false,
|
||||
"closes_at": null,
|
||||
"total_voters": 5,
|
||||
"options": [
|
||||
{
|
||||
"id": 21,
|
||||
"text": "Python",
|
||||
"position": 0,
|
||||
"vote_count": 3,
|
||||
"percentage": 60
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"text": "JavaScript",
|
||||
"position": 1,
|
||||
"vote_count": 2,
|
||||
"percentage": 40
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Frontend (buildPollHtml)
|
||||
|
||||
```html
|
||||
<div class="poll-widget mt-2" data-poll-id="10">
|
||||
<div class="poll-question mb-3">
|
||||
<strong>Какой язык программирования вы предпочитаете?</strong>
|
||||
</div>
|
||||
<div class="poll-options">
|
||||
<div class="poll-option mb-2" data-option-id="21">
|
||||
<button type="button" class="btn btn-outline-secondary btn-poll-option w-100 text-start"
|
||||
data-option-id="21">
|
||||
Python
|
||||
</button>
|
||||
</div>
|
||||
<div class="poll-option mb-2" data-option-id="22">
|
||||
<button type="button" class="btn btn-outline-secondary btn-poll-option w-100 text-start"
|
||||
data-option-id="22">
|
||||
JavaScript
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="poll-footer mt-3">
|
||||
<div class="small text-muted">
|
||||
5 проголосовало
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ Исправления
|
||||
|
||||
### 1. MessageRenderer теперь генерирует HTML для опросов
|
||||
- Добавлен метод `buildPollHtml(poll)`
|
||||
- В `buildMessageHtml` добавлена строка `${pollHtml}`
|
||||
|
||||
### 2. reinitMessageComponents явно инициализирует опросы
|
||||
- Вызывает `window.chatPoll.refreshPoll(pollId, pollEl)`
|
||||
- Это загружает актуальные данные и обновляет UI
|
||||
|
||||
### 3. Полная поддержка редактирования
|
||||
- При редактировании сообщения с опросом он не теряется
|
||||
- Опрос корректно отображается после замены DOM
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
### Сценарий 1: Создание опроса
|
||||
1. Откройте чат
|
||||
2. Нажмите "Создать голосование"
|
||||
3. Заполните вопрос и варианты
|
||||
4. Отправьте
|
||||
5. **Ожидается**: Опрос появляется в чате с кнопками для голосования
|
||||
|
||||
### Сценарий 2: Редактирование сообщения с опросом
|
||||
1. Создайте сообщение с опросом
|
||||
2. Отредактируйте **текст** сообщения (сам опрос редактировать нельзя)
|
||||
3. **Ожидается**:
|
||||
- Текст обновлён ✅
|
||||
- Опрос остался на месте ✅
|
||||
- Кнопки опроса работают ✅
|
||||
|
||||
### Сценарий 3: Голосование
|
||||
1. Откройте сообщение с опросом
|
||||
2. Нажмите на вариант ответа
|
||||
3. **Ожидается**:
|
||||
- Голос зарегистрирован
|
||||
- Опрос обновлён с результатами
|
||||
- Показаны проценты
|
||||
|
||||
---
|
||||
|
||||
## 📝 Итог
|
||||
|
||||
✅ **Опросы загружаются** через `serialize_message` в payload
|
||||
✅ **MessageRenderer генерирует HTML** через `buildPollHtml()`
|
||||
✅ **ChatPoll инициализирует** через `refreshPoll()`
|
||||
✅ **При редактировании опросы сохраняются** через `reinitMessageComponents()`
|
||||
|
||||
**Все проблемы решены!** 🎉
|
||||
@ -1,153 +0,0 @@
|
||||
# Исправления выделения сообщений
|
||||
|
||||
## Исправленные проблемы
|
||||
|
||||
### 1. ✅ Чекбоксы теперь внутри окна чата
|
||||
|
||||
**Было:** Чекбоксы рисовались за пределами окна (`left: -35px`)
|
||||
|
||||
**Стало:**
|
||||
- Чекбоксы внутри сообщений (`left: 8px`, `right: 8px`)
|
||||
- Добавлены отступы для сообщений в режиме выделения:
|
||||
- Левые сообщения: `padding-left: 40px`
|
||||
- Правые сообщения: `padding-right: 40px`
|
||||
- Адаптивные размеры для мобильных
|
||||
|
||||
### 2. ✅ Убрано долгое нажатие мышью на десктопе
|
||||
|
||||
**Было:** Долгое нажатие (500мс) активировало режим выделения
|
||||
|
||||
**Стало:**
|
||||
- Только для мобильных устройств (touchstart/touchend)
|
||||
- На десктопе работает только через контекстное меню
|
||||
|
||||
### 3. ✅ Вход в режим через контекстное меню
|
||||
|
||||
**Механизм:**
|
||||
1. Правый клик по сообщению → открывается контекстное меню
|
||||
2. Клик на "Выбрать" → активируется режим выделения
|
||||
3. Это сообщение автоматически выделяется
|
||||
4. Остальные сообщения можно выделять левым кликом
|
||||
|
||||
**Реализация:**
|
||||
- Добавлен обработчик `handleSelectAction()` в `messageContextMenu.js`
|
||||
- Отправка события `message-context-menu` с action='select'
|
||||
- Прием события в `messageSelection.js` и вызов `enterSelectionMode()`
|
||||
|
||||
## Изменённые файлы
|
||||
|
||||
### 1. `backend/static/css/message-selection.css`
|
||||
|
||||
```css
|
||||
/* Основные изменения */
|
||||
.selection-mode .msg {
|
||||
padding-left: 40px !important; /* Место для чекбокса */
|
||||
}
|
||||
|
||||
.msg-select-checkbox {
|
||||
left: 8px; /* Внутри сообщения, было -35px */
|
||||
}
|
||||
|
||||
.msg.justify-content-end .msg-select-checkbox {
|
||||
right: 8px; /* Внутри сообщения, было -35px */
|
||||
}
|
||||
```
|
||||
|
||||
### 2. `backend/static/js/components/messageSelection.js`
|
||||
|
||||
**Удалено:**
|
||||
- Обработчики `mousedown`, `mouseup`, `mousemove` для десктопа
|
||||
- Таймер долгого нажатия мышью
|
||||
|
||||
**Оставлено:**
|
||||
- Touch события для мобильных
|
||||
- Обработчик события `message-context-menu`
|
||||
- Метод `enterSelectionMode()` как публичный API
|
||||
|
||||
### 3. `backend/static/js/components/messageContextMenu.js`
|
||||
|
||||
**Добавлено:**
|
||||
```javascript
|
||||
case 'select':
|
||||
this.handleSelectAction(messageId);
|
||||
break;
|
||||
|
||||
handleSelectAction(messageId) {
|
||||
const messageElement = document.querySelector(`[data-id="${messageId}"]`);
|
||||
window.dispatchEvent(new CustomEvent('message-context-menu', {
|
||||
detail: {
|
||||
action: 'select',
|
||||
messageId: messageId,
|
||||
messageElement: messageElement
|
||||
}
|
||||
}));
|
||||
}
|
||||
```
|
||||
|
||||
## Как использовать
|
||||
|
||||
### На компьютере:
|
||||
1. **Правый клик** по любому сообщению
|
||||
2. В меню выбрать **"Выбрать"**
|
||||
3. Сообщение выделится автоматически
|
||||
4. **Левый клик** по другим сообщениям для выделения
|
||||
5. Кнопки действий внизу экрана
|
||||
|
||||
### На телефоне:
|
||||
1. **Долгое нажатие** (500мс) на сообщение
|
||||
2. Или правый клик → "Выбрать"
|
||||
3. Тап по другим сообщениям для выделения
|
||||
4. Кнопки действий внизу экрана
|
||||
|
||||
## Визуальные улучшения
|
||||
|
||||
### Чекбоксы:
|
||||
- ✅ Находятся внутри границ окна чата
|
||||
- ✅ Не перекрывают содержимое сообщений
|
||||
- ✅ Адаптивный размер (24px → 20px на мобильных)
|
||||
- ✅ Правильное позиционирование для своих/чужих сообщений
|
||||
|
||||
### Режим выделения:
|
||||
- ✅ Hover эффект на сообщениях
|
||||
- ✅ Подсветка выделенных сообщений
|
||||
- ✅ Синие чекбоксы с галочкой
|
||||
- ✅ Счетчик выделенных сообщений
|
||||
|
||||
### Панель действий:
|
||||
- ✅ Фиксированная позиция внизу
|
||||
- ✅ Анимация появления
|
||||
- ✅ Кнопки: Отмена, Удалить, Переслать
|
||||
- ✅ Адаптивный дизайн
|
||||
|
||||
## Проверка
|
||||
|
||||
Обновите страницу и проверьте:
|
||||
|
||||
1. **Чекбоксы видны полностью**
|
||||
- Не выходят за границы чата
|
||||
- Не перекрывают текст сообщений
|
||||
|
||||
2. **Десктоп работает правильно**
|
||||
- Нет реакции на долгое нажатие ЛКМ
|
||||
- ПКМ → "Выбрать" → режим активируется
|
||||
- ЛКМ выделяет/снимает выделение
|
||||
|
||||
3. **Мобильный работает правильно**
|
||||
- Долгое нажатие активирует режим
|
||||
- Вибрация при активации
|
||||
- Тап выделяет/снимает выделение
|
||||
|
||||
## Совместимость
|
||||
|
||||
- ✅ Chrome/Edge/Opera
|
||||
- ✅ Firefox
|
||||
- ✅ Safari (desktop/mobile)
|
||||
- ✅ Chrome Mobile
|
||||
- ✅ Планшеты
|
||||
|
||||
## Производительность
|
||||
|
||||
- Минимальное использование DOM манипуляций
|
||||
- Делегирование событий
|
||||
- Оптимизированные CSS transitions
|
||||
- Отсутствие утечек памяти (cleanup при выходе)
|
||||
92
backend/docs/README.md
Normal file
92
backend/docs/README.md
Normal file
@ -0,0 +1,92 @@
|
||||
# Backend Documentation
|
||||
|
||||
Техническая документация по backend части проекта EUSRR.
|
||||
|
||||
## 📁 Структура
|
||||
|
||||
### [guides/](guides/)
|
||||
**Руководства разработчика** - инструкции по работе с различными модулями
|
||||
|
||||
- CONTEXT_MENU_GUIDE.md - работа с контекстными меню
|
||||
- REACTIONS_GUIDE.md - система реакций
|
||||
|
||||
### [troubleshooting/](troubleshooting/)
|
||||
**Отладка и исправления** - диагностика проблем
|
||||
|
||||
- CONTEXT_MENU_DEBUG.md - отладка контекстного меню
|
||||
- MESSAGE_DELETE_DEBUG.md - отладка удаления сообщений
|
||||
- MESSAGE_DELETE_FIX.md - исправление проблем удаления
|
||||
|
||||
### [architecture/](architecture/)
|
||||
**Архитектурная документация** - устройство систем
|
||||
|
||||
- API_ANALYSIS.md - анализ API
|
||||
- API_REFACTORING_SUMMARY.md - результаты рефакторинга API
|
||||
- VIEWSET_ANALYSIS.md - анализ ViewSet
|
||||
|
||||
### [testing/](testing/)
|
||||
**Тестирование** - планы и результаты тестирования
|
||||
|
||||
- POSITION_PERMISSIONS_TESTING.md - тестирование прав должностей
|
||||
- REQUESTS_API_TESTING_PLAN.md - план тестирования API заявок
|
||||
- TEST_IMAGE_SCRIPTS.md - скрипты тестирования изображений
|
||||
|
||||
### [reports/](reports/)
|
||||
**Отчеты и аудиты** - результаты проверок и реорганизаций
|
||||
|
||||
- BACKEND_CLEANUP_AUDIT.md - аудит очистки backend/
|
||||
- REQUESTS_API_TESTING_PLAN.md - план тестирования API заявок
|
||||
- TEST_IMAGE_SCRIPTS.md - скрипты тестирования изображений
|
||||
|
||||
## 🔍 Навигация
|
||||
|
||||
### По типу задачи:
|
||||
|
||||
- **Как работает X?** → [architecture/](architecture/)
|
||||
- **Как настроить Y?** → [guides/](guides/)
|
||||
- **Проблема с Z** → [troubleshooting/](troubleshooting/)
|
||||
- **Как тестировать?** → [testing/](testing/)
|
||||
|
||||
### Связанная документация:
|
||||
|
||||
- [/docs/](../../docs/) - общая документация проекта
|
||||
- [backend/scripts/](../scripts/) - вспомогательные скрипты
|
||||
|
||||
## 📝 Правила
|
||||
|
||||
### Добавление новой документации:
|
||||
|
||||
1. **Guides** - постоянные инструкции, reference материалы
|
||||
2. **Troubleshooting** - временные документы, после решения → удалить
|
||||
3. **Architecture** - долгосрочная документация, обновлять при изменениях
|
||||
4. **Testing** - планы тестирования, после завершения → архивировать
|
||||
|
||||
### Формат файлов:
|
||||
|
||||
```markdown
|
||||
# Заголовок
|
||||
|
||||
**Дата:** DD.MM.YYYY
|
||||
**Автор:** Имя
|
||||
**Статус:** Актуально / Устарело / В процессе
|
||||
|
||||
## Описание
|
||||
...
|
||||
|
||||
## Использование
|
||||
...
|
||||
|
||||
## Примеры
|
||||
...
|
||||
```
|
||||
|
||||
## 🔄 Жизненный цикл
|
||||
|
||||
1. **Создание** - документируйте новую фичу/проблему
|
||||
2. **Актуализация** - обновляйте при изменениях
|
||||
3. **Архивация** - после завершения/решения
|
||||
4. **Удаление** - если больше не актуально
|
||||
|
||||
---
|
||||
|
||||
**Поддерживайте документацию актуальной!** 📚
|
||||
320
backend/docs/reports/BACKEND_CLEANUP_AUDIT.md
Normal file
320
backend/docs/reports/BACKEND_CLEANUP_AUDIT.md
Normal file
@ -0,0 +1,320 @@
|
||||
# Аудит папки backend/
|
||||
|
||||
**Дата:** 26 декабря 2025 г.
|
||||
**Задача:** Очистка главной папки Django приложения
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Найдено лишних файлов
|
||||
|
||||
### 📊 Статистика
|
||||
|
||||
| Тип файлов | Количество | Категория |
|
||||
|------------|------------|-----------|
|
||||
| test_*.py | 22 файла | Тестовые скрипты |
|
||||
| check_*.py | 5 файлов | Диагностические проверки |
|
||||
| *_test*.py и утилиты | 7 файлов | Вспомогательные скрипты |
|
||||
| *.md | 12 файлов | Техническая документация |
|
||||
| *.sh | 1 файл | Shell скрипты |
|
||||
| ldap_test_* | 2 папки | Тестовые данные LDAP |
|
||||
| **ИТОГО** | **49 файлов/папок** | Требуют реорганизации |
|
||||
|
||||
---
|
||||
|
||||
## 📂 Детальный список
|
||||
|
||||
### 1. Тестовые скрипты (22 файла)
|
||||
|
||||
**test_*.py** - ручные тесты для быстрой проверки:
|
||||
```
|
||||
test_all_signals.py # Тестирование сигналов
|
||||
test_announcement_notifications.py # Уведомления объявлений
|
||||
test_avatar_simple.py # Простой тест аватаров
|
||||
test_corporate_ip.py # Корпоративные IP
|
||||
test_create_announcement_message.py # Создание объявлений
|
||||
test_email.py # Email функционал
|
||||
test_ip_restrictions.py # IP ограничения
|
||||
test_ip_simple.py # Простой тест IP
|
||||
test_ldap_simple.py # Простой тест LDAP
|
||||
test_message_serialization.py # Сериализация сообщений
|
||||
test_notification_create.py # Создание уведомлений
|
||||
test_notification_debug.py # Отладка уведомлений
|
||||
test_notification_signals.py # Сигналы уведомлений
|
||||
test_pagination.py # Пагинация
|
||||
test_phone_export.py # Экспорт телефонов
|
||||
test_reactions_api.py # API реакций
|
||||
test_recipients.py # Получатели
|
||||
test_recipients_api.py # API получателей
|
||||
test_register_api.py # API регистрации
|
||||
test_reply_update.py # Обновление ответов
|
||||
test_requests_access.py # Доступ к заявкам
|
||||
test_requests_access_detailed.py # Детальный доступ к заявкам
|
||||
```
|
||||
|
||||
**Рекомендация:** Переместить в `backend/scripts/manual_tests/`
|
||||
|
||||
---
|
||||
|
||||
### 2. Диагностические скрипты (5 файлов)
|
||||
|
||||
**check_*.py** - проверка состояния системы:
|
||||
```
|
||||
check_chat_access.py # Проверка доступа к чатам
|
||||
check_ldap_user.py # Проверка пользователя LDAP
|
||||
check_pagination.py # Проверка пагинации
|
||||
check_reactions.py # Проверка реакций
|
||||
check_templates.py # Проверка шаблонов
|
||||
```
|
||||
|
||||
**Рекомендация:** Переместить в `backend/scripts/diagnostic/`
|
||||
|
||||
---
|
||||
|
||||
### 3. Вспомогательные скрипты (8 файлов)
|
||||
|
||||
**Утилиты и генераторы:**
|
||||
```
|
||||
analyze_templates.py # Анализ шаблонов
|
||||
create_test_data.py # Создание тестовых данных
|
||||
diagnose_page.py # Диагностика страниц
|
||||
diagnostic_summary.py # Сводка диагностики
|
||||
generate_notification.py # Генерация уведомлений
|
||||
quick_test.py # Быстрые тесты
|
||||
render_test_page.py # Рендеринг тестовых страниц
|
||||
simple_create_users.py # Простое создание пользователей
|
||||
```
|
||||
|
||||
**Рекомендация:**
|
||||
- Утилиты → `backend/scripts/utils/`
|
||||
- Диагностика → `backend/scripts/diagnostic/`
|
||||
|
||||
---
|
||||
|
||||
### 4. MD документация (12 файлов)
|
||||
|
||||
**Техническая документация в корне backend/:**
|
||||
```
|
||||
API_ANALYSIS.md # Анализ API
|
||||
API_REFACTORING_SUMMARY.md # Рефакторинг API
|
||||
CONTEXT_MENU_DEBUG.md # Отладка контекстного меню
|
||||
CONTEXT_MENU_GUIDE.md # Руководство контекстного меню
|
||||
MESSAGE_DELETE_DEBUG.md # Отладка удаления сообщений
|
||||
MESSAGE_DELETE_FIX.md # Исправление удаления
|
||||
POSITION_PERMISSIONS_TESTING.md # Тестирование прав должностей
|
||||
REACTIONS_GUIDE.md # Руководство по реакциям
|
||||
REACTIONS_IMPLEMENTATION.md # Реализация реакций
|
||||
REQUESTS_API_TESTING_PLAN.md # План тестирования API заявок
|
||||
TEST_IMAGE_SCRIPTS.md # Скрипты тестирования изображений
|
||||
VIEWSET_ANALYSIS.md # Анализ ViewSet
|
||||
```
|
||||
|
||||
**Рекомендация:**
|
||||
- GUIDE → переместить в `backend/docs/guides/`
|
||||
- DEBUG/FIX → переместить в `backend/docs/troubleshooting/`
|
||||
- ANALYSIS → переместить в `backend/docs/architecture/`
|
||||
- TESTING → переместить в `backend/docs/testing/`
|
||||
|
||||
---
|
||||
|
||||
### 5. Shell скрипты (1 файл)
|
||||
|
||||
```
|
||||
ldap-test.sh # Тестирование LDAP сервера
|
||||
```
|
||||
|
||||
**Рекомендация:** Переместить в `backend/scripts/ldap/`
|
||||
|
||||
---
|
||||
|
||||
### 6. Тестовые данные LDAP (2 папки)
|
||||
|
||||
```
|
||||
ldap_test_config/ # Конфигурация тестового LDAP
|
||||
ldap_test_data/ # Данные тестового LDAP
|
||||
```
|
||||
|
||||
**Содержимое:**
|
||||
- `ldap_test_config/` - конфигурационные файлы OpenLDAP
|
||||
- `ldap_test_data/` - файлы базы данных (data.mdb, lock.mdb)
|
||||
|
||||
**Рекомендация:**
|
||||
- Если используются → переместить в `backend/tests/fixtures/ldap/`
|
||||
- Если не используются → можно удалить (данные автоматически генерируются)
|
||||
|
||||
---
|
||||
|
||||
## 🗂️ Предлагаемая структура
|
||||
|
||||
```
|
||||
backend/
|
||||
├── manage.py # Основной файл Django
|
||||
├── scripts/ # Вспомогательные скрипты
|
||||
│ ├── manual_tests/ # test_*.py (22 файла)
|
||||
│ ├── diagnostic/ # check_*.py (5 файлов)
|
||||
│ ├── utils/ # create_*, generate_* (утилиты)
|
||||
│ └── ldap/ # ldap-test.sh
|
||||
├── docs/ # Документация backend
|
||||
│ ├── guides/ # *_GUIDE.md
|
||||
│ ├── troubleshooting/ # *_DEBUG.md, *_FIX.md
|
||||
│ ├── architecture/ # *_ANALYSIS.md
|
||||
│ └── testing/ # *_TESTING*.md
|
||||
├── tests/ # Официальные тесты
|
||||
│ └── fixtures/
|
||||
│ └── ldap/ # ldap_test_config/, ldap_test_data/
|
||||
└── [Django apps...] # api/, employees/, etc.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ План действий
|
||||
|
||||
### Шаг 1: Создать структуру папок
|
||||
```bash
|
||||
mkdir -p backend/scripts/{manual_tests,diagnostic,utils,ldap}
|
||||
mkdir -p backend/docs/{guides,troubleshooting,architecture,testing}
|
||||
```
|
||||
|
||||
### Шаг 2: Переместить тестовые скрипты (22)
|
||||
```bash
|
||||
mv backend/test_*.py backend/scripts/manual_tests/
|
||||
```
|
||||
|
||||
### Шаг 3: Переместить диагностические скрипты (5)
|
||||
```bash
|
||||
mv backend/check_*.py backend/scripts/diagnostic/
|
||||
mv backend/diagnostic_summary.py backend/scripts/diagnostic/
|
||||
mv backend/diagnose_page.py backend/scripts/diagnostic/
|
||||
```
|
||||
|
||||
### Шаг 4: Переместить утилиты (5)
|
||||
```bash
|
||||
mv backend/analyze_templates.py backend/scripts/utils/
|
||||
mv backend/create_test_data.py backend/scripts/utils/
|
||||
mv backend/generate_notification.py backend/scripts/utils/
|
||||
mv backend/quick_test.py backend/scripts/utils/
|
||||
mv backend/render_test_page.py backend/scripts/utils/
|
||||
mv backend/simple_create_users.py backend/scripts/utils/
|
||||
```
|
||||
|
||||
### Шаг 5: Переместить shell скрипты (1)
|
||||
```bash
|
||||
mv backend/ldap-test.sh backend/scripts/ldap/
|
||||
```
|
||||
|
||||
### Шаг 6: Реорганизовать MD файлы (12)
|
||||
|
||||
**Guides (2):**
|
||||
```bash
|
||||
mv backend/CONTEXT_MENU_GUIDE.md backend/docs/guides/
|
||||
mv backend/REACTIONS_GUIDE.md backend/docs/guides/
|
||||
```
|
||||
|
||||
**Troubleshooting (2):**
|
||||
```bash
|
||||
mv backend/CONTEXT_MENU_DEBUG.md backend/docs/troubleshooting/
|
||||
mv backend/MESSAGE_DELETE_DEBUG.md backend/docs/troubleshooting/
|
||||
mv backend/MESSAGE_DELETE_FIX.md backend/docs/troubleshooting/
|
||||
```
|
||||
|
||||
**Architecture (3):**
|
||||
```bash
|
||||
mv backend/API_ANALYSIS.md backend/docs/architecture/
|
||||
mv backend/VIEWSET_ANALYSIS.md backend/docs/architecture/
|
||||
mv backend/API_REFACTORING_SUMMARY.md backend/docs/architecture/
|
||||
```
|
||||
|
||||
**Testing (3):**
|
||||
```bash
|
||||
mv backend/POSITION_PERMISSIONS_TESTING.md backend/docs/testing/
|
||||
mv backend/REQUESTS_API_TESTING_PLAN.md backend/docs/testing/
|
||||
mv backend/TEST_IMAGE_SCRIPTS.md backend/docs/testing/
|
||||
```
|
||||
|
||||
**Completed (2):**
|
||||
```bash
|
||||
mv backend/REACTIONS_IMPLEMENTATION.md backend/docs/completed/
|
||||
```
|
||||
|
||||
### Шаг 7: LDAP тестовые данные
|
||||
**Опция 1 (если используются):**
|
||||
```bash
|
||||
mkdir -p backend/tests/fixtures/ldap/
|
||||
mv backend/ldap_test_config backend/tests/fixtures/ldap/
|
||||
mv backend/ldap_test_data backend/tests/fixtures/ldap/
|
||||
```
|
||||
|
||||
**Опция 2 (если не используются):**
|
||||
```bash
|
||||
rm -rf backend/ldap_test_config backend/ldap_test_data
|
||||
```
|
||||
|
||||
### Шаг 8: Создать README файлы
|
||||
- `backend/scripts/README.md` - описание всех скриптов
|
||||
- `backend/docs/README.md` - структура документации
|
||||
|
||||
---
|
||||
|
||||
## 📊 Результат
|
||||
|
||||
### До очистки:
|
||||
```
|
||||
backend/
|
||||
├── manage.py
|
||||
├── test_*.py (22 файла)
|
||||
├── check_*.py (5 файлов)
|
||||
├── analyze_*.py и др. (8 файлов)
|
||||
├── *.md (12 файлов)
|
||||
├── ldap-test.sh
|
||||
├── ldap_test_config/
|
||||
├── ldap_test_data/
|
||||
└── [Django apps...]
|
||||
```
|
||||
**ИТОГО в корне:** 49 лишних файлов/папок
|
||||
|
||||
### После очистки:
|
||||
```
|
||||
backend/
|
||||
├── manage.py
|
||||
├── scripts/ # Все скрипты организованы
|
||||
├── docs/ # Вся документация организована
|
||||
├── tests/fixtures/ # Тестовые данные
|
||||
└── [Django apps...] # Только Django приложения
|
||||
```
|
||||
**В корне:** Только manage.py и Django приложения ✨
|
||||
|
||||
---
|
||||
|
||||
## 💡 Дополнительные рекомендации
|
||||
|
||||
### 1. Добавить .gitignore правила
|
||||
```gitignore
|
||||
# LDAP test data (автогенерируется)
|
||||
backend/tests/fixtures/ldap/ldap_test_data/
|
||||
```
|
||||
|
||||
### 2. Документировать скрипты
|
||||
В каждом README.md описать:
|
||||
- Назначение скриптов
|
||||
- Как запускать
|
||||
- Когда использовать
|
||||
- Примеры использования
|
||||
|
||||
### 3. Очистить неиспользуемые скрипты
|
||||
После перемещения проверить:
|
||||
- Какие скрипты устарели
|
||||
- Какие дублируют функционал тестов
|
||||
- Что можно удалить
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Приоритет выполнения
|
||||
|
||||
1. ✅ **Высокий** - Переместить test_* скрипты (захламляют корень)
|
||||
2. ✅ **Высокий** - Переместить check_* скрипты
|
||||
3. ✅ **Средний** - Реорганизовать MD документацию
|
||||
4. ✅ **Средний** - Переместить утилиты
|
||||
5. ⚠️ **Низкий** - Решить судьбу ldap_test_* папок
|
||||
|
||||
---
|
||||
|
||||
**Готов приступить к выполнению?** Начнем с создания структуры и перемещения файлов.
|
||||
31
backend/docs/reports/README.md
Normal file
31
backend/docs/reports/README.md
Normal file
@ -0,0 +1,31 @@
|
||||
# Backend - Отчеты и аудиты
|
||||
|
||||
Отчеты о выполненных работах по очистке и реорганизации backend/.
|
||||
|
||||
## 📋 Отчеты
|
||||
|
||||
### [BACKEND_CLEANUP_AUDIT.md](BACKEND_CLEANUP_AUDIT.md)
|
||||
Аудит папки backend/:
|
||||
- Найдено 49 лишних файлов
|
||||
- 22 test_*.py скрипта
|
||||
- 12 MD файлов документации
|
||||
- План реорганизации
|
||||
- Результаты выполнения
|
||||
|
||||
## 🎯 Назначение
|
||||
|
||||
Документация по:
|
||||
- Аудитам backend кода
|
||||
- Рефакторингу структуры
|
||||
- Миграциям и обновлениям
|
||||
- Анализу производительности
|
||||
|
||||
## 📝 Структура отчета
|
||||
|
||||
Каждый отчет должен содержать:
|
||||
1. Дата и автор
|
||||
2. Цель аудита
|
||||
3. Найденные проблемы
|
||||
4. Статистика "до/после"
|
||||
5. Выполненные действия
|
||||
6. Рекомендации
|
||||
79
backend/scripts/README.md
Normal file
79
backend/scripts/README.md
Normal file
@ -0,0 +1,79 @@
|
||||
# Backend Scripts
|
||||
|
||||
Вспомогательные скрипты для разработки, тестирования и диагностики.
|
||||
|
||||
## 📁 Структура
|
||||
|
||||
### [manual_tests/](manual_tests/)
|
||||
**22 скрипта** - Ручные тесты для быстрой проверки функционала
|
||||
|
||||
Запуск из корня backend:
|
||||
```bash
|
||||
.venv/Scripts/python scripts/manual_tests/test_ldap_simple.py
|
||||
```
|
||||
|
||||
### [diagnostic/](diagnostic/)
|
||||
**8 скриптов** - Диагностика и проверка состояния системы
|
||||
|
||||
```bash
|
||||
.venv/Scripts/python scripts/diagnostic/check_ldap_user.py
|
||||
.venv/Scripts/python scripts/diagnostic/check_chat_access.py
|
||||
```
|
||||
|
||||
### [utils/](utils/)
|
||||
**6 утилит** - Генерация тестовых данных и анализ
|
||||
|
||||
```bash
|
||||
.venv/Scripts/python scripts/utils/create_test_data.py
|
||||
.venv/Scripts/python scripts/utils/generate_notification.py
|
||||
```
|
||||
|
||||
### [ldap/](ldap/)
|
||||
**Shell скрипты** - Тестирование LDAP сервера
|
||||
|
||||
```bash
|
||||
bash scripts/ldap/ldap-test.sh
|
||||
```
|
||||
|
||||
## 🎯 Использование
|
||||
|
||||
### Важно!
|
||||
Всегда используйте виртуальное окружение при запуске скриптов:
|
||||
```bash
|
||||
# Полный путь к Python
|
||||
.venv/Scripts/python scripts/manual_tests/test_*.py
|
||||
|
||||
# Или активируйте venv
|
||||
source .venv/Scripts/activate # Linux/Mac
|
||||
.venv\Scripts\activate # Windows
|
||||
python scripts/manual_tests/test_*.py
|
||||
```
|
||||
|
||||
### Django management команды
|
||||
Для доступа к Django ORM в скриптах используйте:
|
||||
```python
|
||||
import os
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'eusrr_backend.settings')
|
||||
django.setup()
|
||||
```
|
||||
|
||||
## 📝 Рекомендации
|
||||
|
||||
1. **Именование**: Используйте понятные префиксы
|
||||
- `test_*` - тестовые скрипты
|
||||
- `check_*` - проверки
|
||||
- `create_*` - генераторы
|
||||
- `analyze_*` - анализ
|
||||
|
||||
2. **Документация**: Добавляйте docstring в начало каждого скрипта
|
||||
|
||||
3. **Аргументы**: Используйте argparse для параметров
|
||||
|
||||
4. **Очистка**: Удаляйте устаревшие скрипты после интеграции в тесты
|
||||
|
||||
## 🔗 См. также
|
||||
|
||||
- [backend/tests/](../tests/) - Официальные unit/integration тесты
|
||||
- [backend/docs/testing/](../docs/testing/) - Документация по тестированию
|
||||
37
docs/README.md
Normal file
37
docs/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# Документация проекта EUSRR
|
||||
|
||||
Эта папка содержит всю документацию проекта, организованную по категориям.
|
||||
|
||||
## 📁 Структура
|
||||
|
||||
### [completed/](completed/)
|
||||
Завершенные задачи: рефакторинги, исправления, реализованные фичи (22 файла)
|
||||
|
||||
### [guides/](guides/)
|
||||
Активные руководства и инструкции для разработчиков (гайды по валидации, миграции, настройке)
|
||||
|
||||
### [architecture/](architecture/)
|
||||
Архитектурная документация и анализ систем (рендеринг чата, архитектура страниц)
|
||||
|
||||
### [diagnostic/](diagnostic/)
|
||||
Диагностические руководства (WebSocket 1006, отладка проблем)
|
||||
|
||||
### [in-progress/](in-progress/)
|
||||
Активные задачи в разработке (закупки, получатели заявок, календарь)
|
||||
|
||||
### [reports/](reports/)
|
||||
Отчеты и аудиты проекта (реорганизация документации, план очистки)
|
||||
|
||||
## 📝 Главная документация
|
||||
|
||||
- [README.md](../README.md) - основная документация проекта
|
||||
- [docs/reports/](reports/) - отчеты и планы по проекту
|
||||
|
||||
## 🔍 Поиск информации
|
||||
|
||||
- **Завершенные фичи** → `completed/`
|
||||
- **Как настроить X** → `guides/`
|
||||
- **Как работает Y** → `architecture/`
|
||||
- **Проблемы с Z** → `diagnostic/`
|
||||
- **Что в разработке** → `in-progress/`
|
||||
- **Отчеты и аудиты** → `reports/`
|
||||
23
docs/architecture/README.md
Normal file
23
docs/architecture/README.md
Normal file
@ -0,0 +1,23 @@
|
||||
# Архитектурная документация
|
||||
|
||||
Документация по архитектуре и внутреннему устройству систем.
|
||||
|
||||
## 📐 Документы
|
||||
|
||||
### Система чата
|
||||
- **CHAT_PAGE_RENDERING_ARCHITECTURE.md** - архитектура рендеринга страницы чата
|
||||
- **CHAT_RENDERING_ANALYSIS.md** - анализ рендеринга чата
|
||||
|
||||
## 🎯 Назначение
|
||||
|
||||
Эти документы описывают:
|
||||
- Как работают различные компоненты системы
|
||||
- Архитектурные решения и их обоснование
|
||||
- Взаимодействие между модулями
|
||||
- Потоки данных и обработка событий
|
||||
|
||||
Полезно для:
|
||||
- Понимания сложных систем
|
||||
- Рефакторинга
|
||||
- Отладки проблем производительности
|
||||
- Онбординга новых разработчиков
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user