12 KiB
12 KiB
Исправление: WebSocket события для опросов
🐛 Проблемы
1. Новые опросы не встраивались в DOM
- Причина:
create_pollотправлял неправильную структуру WebSocket события - Симптом: После создания опроса он не появлялся, нужна была перезагрузка
2. Информация о проголосовавших не обновлялась
- Причина:
user_consumer.pyне передавал полные данныеresultsна фронтенд - Симптом: После голосования проценты/счётчики не обновлялись в реальном времени
✅ Исправления
1. Backend: poll_views.py - Создание опроса
БЫЛО (неправильная структура):
ws_message = {
'type': 'chat_message', # ← неправильный тип
'payload': {
'message': { # ← лишняя вложенность
'id': message.id,
'content': message.content,
'poll': poll_data # ← poll не сериализован через serialize_message
}
}
}
СТАЛО (правильная структура):
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):
ws_message = {
'type': 'poll_update',
# chat_id отсутствовал! ←
'payload': {
'poll_id': poll.id,
'message_id': poll.message.id,
'results': results
}
}
СТАЛО:
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:
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
БЫЛО (неполные данные):
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):
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):
case 'poll_vote':
handlePollVote(data);
break;
СТАЛО (поддержка обоих типов):
case 'poll_vote':
case 'poll_update': // ← добавлено!
handlePollUpdate(data);
break;
Новая функция handlePollUpdate:
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()):
{
"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 (теперь):
{
"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: Создание опроса
- Откройте чат
- Нажмите "Создать голосование"
- Заполните вопрос: "Какой язык?"
- Добавьте варианты: "Python", "JavaScript"
- Отправьте
Ожидается:
- ✅ Опрос сразу появляется в чате (без перезагрузки)
- ✅ Показаны кнопки для голосования
- ✅ Счётчик: "0 проголосовало"
Сценарий 2: Голосование
- Откройте чат с опросом
- Нажмите на вариант "Python"
Ожидается:
- ✅ Кнопки превращаются в прогресс-бары (у всех пользователей!)
- ✅ Показаны проценты: "Python 100%"
- ✅ Счётчик: "1 проголосовало"
- Другой пользователь голосует за "JavaScript"
Ожидается:
- ✅ Автоматически обновляются проценты: "Python 50%", "JavaScript 50%"
- ✅ Счётчик: "2 проголосовало"
Сценарий 3: Множественные пользователи
- Откройте чат в 2 окнах (разные пользователи)
- В первом окне создайте опрос
- Во втором окне опрос должен появиться сразу
- Проголосуйте во втором окне
- В первом окне результаты должны обновиться автоматически
📝 Изменённые файлы
-
✅
backend/api/v1/communications/poll_views.pycreate_poll: используетserialize_message(), добавленchat_idvote_poll: добавленchat_idв WebSocket eventclose_poll: добавленchat_idв WebSocket event
-
✅
backend/communications/user_consumer.pychat_poll_update: передаёт полныеresultsизpayload
-
✅
backend/static/js/components/userWebSocket.js- Добавлен обработчик
poll_update - Функция
handlePollUpdate()диспатчитchat:poll-updateс полными данными
- Добавлен обработчик
-
✅
backend/static/js/chat-detail-enhanced.js- Уже есть слушатель
chat:poll-update→ вызываетchatPoll.refreshPoll()
- Уже есть слушатель
-
✅
backend/static/js/components/messageRenderer.js- Добавлен
buildPollHtml()для генерации HTML опросов
- Добавлен
-
✅
backend/static/js/components/messageEditing.js- Вызывает
chatPoll.refreshPoll()после редактирования сообщения с опросом
- Вызывает
✅ Результат
ДО исправления:
- ❌ Новые опросы не появлялись (нужна перезагрузка)
- ❌ Результаты голосования не обновлялись в реальном времени
- ❌ Проценты/счётчики замораживались
ПОСЛЕ исправления:
- ✅ Новые опросы появляются мгновенно
- ✅ Результаты голосования обновляются в реальном времени у всех пользователей
- ✅ Проценты/счётчики обновляются автоматически
- ✅ Работает через WebSocket без перезагрузки страницы
Всё работает! 🎉