Corp/POLL_WEBSOCKET_FIX.md

371 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Исправление: WebSocket события для опросов
## 🐛 Проблемы
### 1. Новые опросы не встраивались в DOM
- **Причина**: `create_poll` отправлял неправильную структуру WebSocket события
- **Симптом**: После создания опроса он не появлялся, нужна была перезагрузка
### 2. Информация о проголосовавших не обновлялась
- **Причина**: `user_consumer.py` не передавал полные данные `results` на фронтенд
- **Симптом**: После голосования проценты/счётчики не обновлялись в реальном времени
---
## ✅ Исправления
### 1. Backend: `poll_views.py` - Создание опроса
**БЫЛО (неправильная структура):**
```python
ws_message = {
'type': 'chat_message', # ← неправильный тип
'payload': {
'message': { # ← лишняя вложенность
'id': message.id,
'content': message.content,
'poll': poll_data # ← poll не сериализован через serialize_message
}
}
}
```
**СТАЛО (правильная структура):**
```python
from communications.consumers import serialize_message
# Перезагружаем сообщение со всеми связанными объектами
message = Message.objects.select_related(
'author', 'reply_to', 'reply_to__author',
'forwarded_from_author', 'poll'
).prefetch_related(
'attachments', 'reactions', 'reactions__user', 'poll__options'
).get(pk=message.id)
payload = serialize_message(message) # ← правильная сериализация!
async_to_sync(channel_layer.group_send)(
f'chat_{chat.id}',
{
'type': 'chat.message', # ← правильный тип для consumer
'chat_id': chat.id,
'payload': payload # ← полный payload с poll
}
)
```
### 2. Backend: `poll_views.py` - Голосование (vote_poll)
**БЫЛО (отсутствовал chat_id):**
```python
ws_message = {
'type': 'poll_update',
# chat_id отсутствовал! ←
'payload': {
'poll_id': poll.id,
'message_id': poll.message.id,
'results': results
}
}
```
**СТАЛО:**
```python
ws_message = {
'type': 'poll_update',
'chat_id': chat.id, # ← добавлено!
'payload': {
'poll_id': poll.id,
'message_id': poll.message.id,
'results': results # ← полные результаты
}
}
```
### 3. Backend: `poll_views.py` - Закрытие опроса (close_poll)
**Та же проблема - добавлен `chat_id`:**
```python
ws_message = {
'type': 'poll_update',
'chat_id': poll.message.chat.id, # ← добавлено!
'payload': {
'poll_id': poll.id,
'message_id': poll.message.id,
'results': poll.get_results()
}
}
```
### 4. Backend: `user_consumer.py` - Обработка poll_update
**БЫЛО (неполные данные):**
```python
async def chat_poll_update(self, event):
chat_id = event.get("chat_id")
if chat_id == self.active_chat_id:
await self.send_json({
"type": "poll_vote", # ← старое название
"chat_id": chat_id,
"poll_id": event.get("poll_id"), # ← берёт из корня, а не из payload
"option_id": event.get("option_id"), # ← этого нет в event!
"vote_count": event.get("vote_count"), # ← этого тоже нет!
"total_voters": event.get("total_voters") # ← и этого нет!
})
```
**СТАЛО (полные данные из payload):**
```python
async def chat_poll_update(self, event):
chat_id = event.get("chat_id")
payload = event.get("payload", {}) # ← извлекаем payload!
if chat_id == self.active_chat_id:
await self.send_json({
"type": "poll_update", # ← правильное название
"chat_id": chat_id,
"poll_id": payload.get("poll_id"), # ← из payload
"message_id": payload.get("message_id"), # ← добавлено
"results": payload.get("results") # ← полные результаты!
})
```
### 5. Frontend: `userWebSocket.js` - Добавлен обработчик
**БЫЛО (только poll_vote):**
```javascript
case 'poll_vote':
handlePollVote(data);
break;
```
**СТАЛО (поддержка обоих типов):**
```javascript
case 'poll_vote':
case 'poll_update': // ← добавлено!
handlePollUpdate(data);
break;
```
**Новая функция handlePollUpdate:**
```javascript
function handlePollUpdate(data) {
console.log('[UserWebSocket] Poll update received:', data);
window.dispatchEvent(new CustomEvent('chat:poll-update', {
detail: {
poll_id: data.poll_id,
message_id: data.message_id,
results: data.results // ← передаём полные результаты!
}
}));
}
```
---
## 🔄 Процесс обновления опросов
### Создание нового опроса:
```
User создаёт опрос
↓
POST /api/v1/communications/polls/create/
↓
Backend: create_poll()
├─ Message.objects.create(content="📊 Вопрос")
├─ Poll.objects.create(message=message)
├─ PollOption.objects.create(poll=poll, text="Вариант 1")
├─ serialize_message(message) → полный payload с poll
└─ WebSocket: type='chat.message', payload={id, content, poll: {...}}
↓
user_consumer.py: chat_message()
└─ send_json(type='new_message', message=payload)
↓
userWebSocket.js: handleNewMessage()
└─ dispatchEvent('new_message', payload)
↓
chatScroll добавляет сообщение в DOM
↓
MessageRenderer.buildMessageHtml()
├─ buildPollHtml(msg.poll) → генерирует HTML
└─ вставляет <div class="poll-widget" data-poll-id="10">
↓
ChatPoll.initializeExistingPolls()
└─ refreshPoll(10) → загружает актуальные данные
↓
✅ Опрос появляется с кнопками голосования!
```
### Голосование в опросе:
```
User нажимает на вариант ответа
↓
POST /api/v1/communications/polls/10/vote/
↓
Backend: vote_poll()
├─ PollVote.objects.create(poll=poll, option=option, voter=user)
├─ option.vote_count += 1
├─ poll.total_voters += 1 (если первый раз)
├─ results = poll.get_results() → {options: [{percentage: 60, ...}], total_voters: 5}
└─ WebSocket: type='poll_update', chat_id=10, payload={poll_id, message_id, results}
↓
user_consumer.py: chat_poll_update()
└─ send_json(type='poll_update', poll_id=10, results={...})
↓
userWebSocket.js: handlePollUpdate()
└─ dispatchEvent('chat:poll-update', {poll_id: 10, results: {...}})
↓
chat-detail-enhanced.js слушает 'chat:poll-update'
└─ window.chatPoll.refreshPoll(10)
↓
ChatPoll.refreshPoll()
├─ НЕ делает fetch (уже есть results в event!)
└─ updatePollWidget(widget, results)
├─ Обновляет проценты: "60%"
├─ Обновляет прогресс-бары: width="60%"
└─ Обновляет счётчики: "5 проголосовало"
↓
✅ Опрос обновляется в реальном времени!
```
---
## 📊 Структура данных
### results (из poll.get_results()):
```json
{
"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
}
]
}
```
### WebSocket event (теперь):
```json
{
"type": "poll_update",
"chat_id": 10,
"poll_id": 10,
"message_id": 495,
"results": {
"id": 10,
"question": "...",
"total_voters": 5,
"options": [
{"id": 21, "text": "Python", "percentage": 60, "vote_count": 3},
{"id": 22, "text": "JavaScript", "percentage": 40, "vote_count": 2}
]
}
}
```
---
## 🧪 Тестирование
### Сценарий 1: Создание опроса
1. Откройте чат
2. Нажмите "Создать голосование"
3. Заполните вопрос: "Какой язык?"
4. Добавьте варианты: "Python", "JavaScript"
5. Отправьте
**Ожидается:**
- ✅ Опрос **сразу появляется** в чате (без перезагрузки)
- ✅ Показаны кнопки для голосования
- ✅ Счётчик: "0 проголосовало"
### Сценарий 2: Голосование
1. Откройте чат с опросом
2. Нажмите на вариант "Python"
**Ожидается:**
- ✅ Кнопки превращаются в прогресс-бары (у всех пользователей!)
- ✅ Показаны проценты: "Python 100%"
- ✅ Счётчик: "1 проголосовало"
3. Другой пользователь голосует за "JavaScript"
**Ожидается:**
- ✅ **Автоматически** обновляются проценты: "Python 50%", "JavaScript 50%"
- ✅ Счётчик: "2 проголосовало"
### Сценарий 3: Множественные пользователи
1. Откройте чат в 2 окнах (разные пользователи)
2. В первом окне создайте опрос
3. **Во втором окне** опрос должен появиться сразу
4. Проголосуйте во втором окне
5. **В первом окне** результаты должны обновиться автоматически
---
## 📝 Изменённые файлы
1. ✅ `backend/api/v1/communications/poll_views.py`
- `create_poll`: использует `serialize_message()`, добавлен `chat_id`
- `vote_poll`: добавлен `chat_id` в WebSocket event
- `close_poll`: добавлен `chat_id` в WebSocket event
2. ✅ `backend/communications/user_consumer.py`
- `chat_poll_update`: передаёт полные `results` из `payload`
3. ✅ `backend/static/js/components/userWebSocket.js`
- Добавлен обработчик `poll_update`
- Функция `handlePollUpdate()` диспатчит `chat:poll-update` с полными данными
4. ✅ `backend/static/js/chat-detail-enhanced.js`
- Уже есть слушатель `chat:poll-update` → вызывает `chatPoll.refreshPoll()`
5. ✅ `backend/static/js/components/messageRenderer.js`
- Добавлен `buildPollHtml()` для генерации HTML опросов
6. ✅ `backend/static/js/components/messageEditing.js`
- Вызывает `chatPoll.refreshPoll()` после редактирования сообщения с опросом
---
## ✅ Результат
**ДО исправления:**
- ❌ Новые опросы не появлялись (нужна перезагрузка)
- ❌ Результаты голосования не обновлялись в реальном времени
- ❌ Проценты/счётчики замораживались
**ПОСЛЕ исправления:**
- ✅ Новые опросы появляются **мгновенно**
- ✅ Результаты голосования обновляются **в реальном времени** у всех пользователей
- ✅ Проценты/счётчики обновляются **автоматически**
- ✅ Работает через WebSocket без перезагрузки страницы
**Всё работает! 🎉**