# Реализация голосований (Polls) в мессенджере
## Обзор
Добавлена полная система голосований в мессенджер EUSRR, реализованная по примеру современных мессенджеров (Telegram, WhatsApp).
## Возможности
### Типы голосований:
1. **Обычное голосование** - выбор одного варианта
2. **Множественный выбор** - выбор нескольких вариантов
3. **Анонимное голосование** - не показывается кто голосовал
4. **Викторина** - с правильным ответом, который показывается после голосования
### Функции:
- Создание голосования с 2-10 вариантами ответа
- Визуализация результатов в реальном времени (прогресс-бары)
- Автоматическое закрытие голосования по времени
- Отображение количества проголосовавших
- Изменение голоса (можно переголосовать)
- Real-time обновление через WebSocket
## Структура базы данных
### Модели (backend/communications/models.py)
#### Poll
```python
- message: OneToOne -> Message (связь с сообщением)
- author: FK -> User (создатель)
- question: CharField (вопрос)
- is_anonymous: Boolean (анонимность)
- is_multiple_choice: Boolean (множественный выбор)
- is_quiz: Boolean (викторина)
- allows_custom_answers: Boolean (свои варианты)
- is_closed: Boolean (закрыто)
- closed_at: DateTime (время закрытия)
- closes_at: DateTime (автозакрытие)
- total_voters: Integer (количество проголосовавших)
```
#### PollOption
```python
- poll: FK -> Poll
- text: CharField (текст варианта)
- position: Integer (порядок)
- vote_count: Integer (количество голосов)
- is_correct: Boolean (правильный для викторин)
```
#### PollVote
```python
- poll: FK -> Poll
- option: FK -> PollOption
- voter: FK -> User
- voted_at: DateTime
UniqueConstraint: poll + voter + option
```
## API Endpoints
### 1. Создать голосование
```http
POST /api/v1/communications/polls/create/
Body:
{
"chat_id": 1,
"question": "Какой язык программирования лучше?",
"options": ["Python", "JavaScript", "Go", "Rust"],
"is_anonymous": false,
"is_multiple_choice": false,
"is_quiz": false,
"correct_option_index": null, // для викторин
"closes_in_minutes": 60 // опционально
}
Response:
{
"success": true,
"poll_id": 123,
"message_id": 456
}
```
### 2. Проголосовать
```http
POST /api/v1/communications/polls/{poll_id}/vote/
Body:
{
"poll_id": 123,
"option_ids": [1] // или [1, 2] для множественного
}
Response:
{
"success": true,
"results": {
"question": "...",
"total_voters": 10,
"is_closed": false,
"options": [...]
}
}
```
### 3. Закрыть голосование
```http
POST /api/v1/communications/polls/{poll_id}/close/
Body:
{
"poll_id": 123
}
Response:
{
"success": true
}
```
### 4. Получить результаты
```http
GET /api/v1/communications/polls/{poll_id}/results/
Response:
{
"question": "...",
"total_voters": 10,
"is_closed": false,
"is_anonymous": false,
"is_multiple_choice": false,
"is_quiz": false,
"user_voted_option_ids": [1],
"options": [
{
"id": 1,
"text": "Python",
"vote_count": 5,
"percentage": 50.0,
"is_correct": false,
"voters": [...] // если не анонимное
}
]
}
```
## Frontend компоненты
### JavaScript модуль (chatPoll.js)
```javascript
import ChatPoll from './components/chatPoll.js';
// Инициализация
const chatPoll = new ChatPoll({
chatId: 1
});
// Создание голосования через модальное окно
// (автоматически привязано к кнопке "Создать голосование")
// Голосование
await chatPoll.vote(pollId, [optionId]);
// Закрытие
await chatPoll.closePoll(pollId);
// Рендер HTML
const html = ChatPoll.renderPoll(pollData, userVotedOptionIds);
```
### CSS стили (chat-polls.css)
- `.poll-widget` - контейнер голосования
- `.poll-question` - вопрос
- `.poll-option` - вариант ответа
- `.poll-option.voted` - выбранный вариант
- `.poll-option-result` - результат с прогресс-баром
- `.poll-footer` - подвал с информацией
## UI/UX
### Меню вложений
В меню прикрепления файлов (bi-paperclip) добавлен пункт:
```html
Создать голосование
```
### Модальное окно создания
- Поле ввода вопроса (до 500 символов)
- Динамический список вариантов (2-10)
- Кнопки добавления/удаления вариантов
- Чекбоксы настроек (анонимность, множественный выбор, викторина)
- Выбор правильного ответа (для викторин)
- Таймер автозакрытия (опционально)
### Отображение голосования в сообщении
**До голосования:**
- Вопрос (жирным)
- Кнопки с вариантами ответа
- Информация о настройках
**После голосования:**
- Вопрос
- Прогресс-бары с процентами
- Галочка на выбранном варианте
- Список проголосовавших (если не анонимное)
- Кнопка "Отменить голос" (если не закрыто)
## WebSocket интеграция
### Отправка нового голосования
```python
channel_layer.group_send(f'chat_{chat_id}', {
'type': 'chat_message',
'message': {
'id': message.id,
'content': '📊 Вопрос',
'poll': {
'id': poll.id,
'question': '...',
'options': [...]
}
}
})
```
### Обновление результатов
```python
channel_layer.group_send(f'chat_{chat_id}', {
'type': 'poll_update',
'poll_id': poll.id,
'message_id': message.id,
'results': poll.get_results()
})
```
## Миграция
```bash
cd backend
python manage.py migrate communications
```
Миграция `0019_add_polls.py` создаст:
- Таблицу `communications_poll`
- Таблицу `communications_polloption`
- Таблицу `communications_pollvote`
- Индексы для производительности
- Констрейнт уникальности голоса
## Админка Django
Зарегистрированы модели:
- `PollAdmin` - управление голосованиями
- `PollOptionAdmin` - управление вариантами
- `PollVoteAdmin` - просмотр голосов
Доступно:
- Просмотр всех голосований с фильтрами
- Inline редактирование вариантов
- Просмотр списка проголосовавших
- Ручное закрытие голосований
## Следующие шаги (опционально)
1. **Добавить поддержку пользовательских вариантов**
- Пользователи могут добавлять свои варианты
- Модерация новых вариантов
2. **Уведомления**
- Уведомление о новом голосовании
- Уведомление о закрытии голосования
- Напоминание проголосовать
3. **Статистика**
- Экспорт результатов в CSV/Excel
- Визуализация (графики, диаграммы)
- История голосований
4. **Расширенные настройки**
- Ограничение по ролям (кто может создавать)
- Видимость результатов до закрытия
- Комментарии к вариантам
## Тестирование
### Ручное тестирование:
1. Открыть чат
2. Нажать на скрепку → "Создать голосование"
3. Заполнить форму, добавить варианты
4. Отправить
5. Проголосовать в другом браузере/инкогнито
6. Проверить обновление результатов в реальном времени
### Тестовые сценарии:
- [ ] Создание обычного голосования
- [ ] Создание анонимного голосования
- [ ] Создание с множественным выбором
- [ ] Создание викторины
- [ ] Голосование в открытом голосовании
- [ ] Изменение голоса
- [ ] Закрытие голосования (автором)
- [ ] Автозакрытие по времени
- [ ] Real-time обновление для других пользователей
- [ ] Отображение в мобильной версии
## Troubleshooting
### Голосование не создаётся
- Проверить миграции: `python manage.py migrate`
- Проверить CSRF токен в запросе
- Проверить права пользователя на отправку в чат
### Результаты не обновляются
- Проверить WebSocket соединение (консоль браузера)
- Проверить Channels/Redis настройки
- Проверить channel_layer в consumers.py
### Стили не применяются
- Проверить подключение chat-polls.css в шаблоне
- Очистить staticfiles: `python manage.py collectstatic --clear`
- Проверить загрузку в DevTools → Network
## Файлы изменений
### Backend:
- `communications/models.py` - модели Poll, PollOption, PollVote
- `communications/migrations/0019_add_polls.py` - миграция
- `communications/admin.py` - админка для голосований
- `api/v1/communications/poll_views.py` - API endpoints
- `api/v1/urls.py` - маршруты API
### Frontend:
- `static/js/components/chatPoll.js` - основной модуль
- `static/css/components/chat-polls.css` - стили
- `static/js/chat-detail-enhanced.js` - интеграция
- `templates/communications/chat_detail.html` - UI (модалка, кнопка)
## Производительность
### Оптимизации:
- Индексы на poll, option, voter
- Денормализация vote_count (избегаем COUNT на каждый запрос)
- Денормализация total_voters
- Prefetch для результатов с вариантами и голосами
### Рекомендации:
- Для больших чатов (>1000 участников) кешировать результаты
- Периодически обновлять vote_count через Celery задачу
- Ограничить частоту обновлений WebSocket (throttling)
---
**Автор:** GitHub Copilot
**Дата:** 30 ноября 2025 г.