# Анализ: Загрузка и встраивание опросов (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 `
${msg.content} ${attachmentsHtml}
`; } ``` **ПОСЛЕ исправления:** ```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 `
${msg.content} ${attachmentsHtml} ${pollHtml} ← добавлено!
`; } // ✅ Новый метод buildPollHtml(poll) { if (!poll || !poll.id) return ''; const totalVotes = poll.total_voters || 0; const isClosed = poll.is_closed || false; return `
${this.escapeHtml(poll.question)}
${poll.options?.map(option => `
`).join('') || ''}
`; } ``` --- ### 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) →
↓ 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
Какой язык программирования вы предпочитаете?
``` --- ## ✅ Исправления ### 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()` **Все проблемы решены!** 🎉