Corp/docs/completed/FORWARDED_MESSAGE_METADATA.md
Игорь Надеин 48631f43fd Реорганизация документации и скриптов проекта
- Создана структура 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 приложения
- Вся документация организована в структурированные папки
2025-12-26 13:49:51 +03:00

14 KiB
Raw Blame History

Отображение метаданных пересланных сообщений

Запрос пользователя

"При пересылке сообщений как-то можно смотреть кто отправитель и во сколько сообщение было отправлено в оригинальном чате?"

Проблема

При пересылке сообщений в текущей реализации отображалось только:

  • ✅ Автор оригинального сообщения (forwarded_from_author)
  • ✅ ID оригинального сообщения (forwarded_from_message_id)

НЕ отображалось:

  • ❌ Дата и время оригинального сообщения
  • ❌ Название чата, из которого переслано сообщение

Решение

Добавлены новые поля в модель Message и обновлен UI для отображения полной информации о пересылке.

1. Новые поля модели (backend/communications/models.py)

class Message(models.Model):
    # ... существующие поля
    
    # Флаги для специальных типов сообщений
    is_forwarded = models.BooleanField(default=False)
    forwarded_from_message_id = models.IntegerField(...)
    forwarded_from_author = models.ForeignKey(...)
    
    # 🆕 Новые поля
    forwarded_from_created_at = models.DateTimeField(
        null=True,
        blank=True,
        help_text="Дата создания исходного сообщения при пересылке"
    )
    forwarded_from_chat_name = models.CharField(
        max_length=255,
        null=True,
        blank=True,
        help_text="Название исходного чата при пересылке"
    )

2. Обновлена логика пересылки (backend/api/v1/communications/views.py)

def forward_messages(request):
    # ...
    for original_msg in messages:
        # Получаем название исходного чата
        source_chat_name = original_msg.chat.name or "Чат"
        if original_msg.chat.type == "private":
            # Для личных чатов показываем "от кого"
            other_user = original_msg.chat.get_other_user(request.user)
            if other_user:
                source_chat_name = (
                    other_user.get_full_name() or
                    other_user.username
                )
        
        # Создаем переслаанное сообщение с метаданными
        forwarded_msg = Message.objects.create(
            chat=target_chat,
            author=request.user,
            content=forwarded_content,
            is_forwarded=True,
            forwarded_from_message_id=original_msg.id,
            forwarded_from_author=original_msg.author,
            forwarded_from_created_at=original_msg.created_at,  # 🆕
            forwarded_from_chat_name=source_chat_name,          # 🆕
        )

3. Обновлена сериализация WebSocket (backend/communications/consumers.py)

def serialize_message(m: Message) -> dict:
    # ...
    
    # Информация о пересылке
    if m.is_forwarded and m.forwarded_from_author:
        forwarded_data = {
            "author_id": m.forwarded_from_author.id,
            "author_name": (
                m.forwarded_from_author.get_full_name()
                or m.forwarded_from_author.username
            ),
            "message_id": m.forwarded_from_message_id,
        }
        
        # 🆕 Добавляем дату оригинального сообщения
        if m.forwarded_from_created_at:
            forwarded_data["created_at"] = (
                m.forwarded_from_created_at.strftime("%d.%m.%Y %H:%M")
            )
            forwarded_data["created_ts"] = int(
                m.forwarded_from_created_at.timestamp() * 1000
            )
        
        # 🆕 Добавляем название исходного чата
        if m.forwarded_from_chat_name:
            forwarded_data["chat_name"] = m.forwarded_from_chat_name
        
        data["forwarded_from"] = forwarded_data

4. Обновлен UI шаблон (backend/static/js/components/chatMessageTemplates.js)

export function createMessageElement(msg, options = {}) {
  // ...
  
  // 🆕 Формирование информации о пересылке
  let forwardedHTML = '';
  if (msg.is_forwarded && msg.forwarded_from) {
    const fwd = msg.forwarded_from;
    const fwdAuthor = esc(fwd.author_name || 'Неизвестный');
    const fwdTime = fwd.created_at ? fwd.created_at : '';
    const fwdChat = fwd.chat_name ? ` из «${esc(fwd.chat_name)}»` : '';
    
    forwardedHTML = `
      <div class="forwarded-indicator small text-secondary mb-2 d-flex align-items-center">
        <i class="bi-arrow-90deg-right me-2"></i>
        <div>
          <div>Переслано от <strong>${fwdAuthor}</strong>${fwdChat}</div>
          ${fwdTime ? `<div class="small opacity-75">${fwdTime}</div>` : ''}
        </div>
      </div>`;
  }
  
  const bubble = `
    <div class="...">
      <div class="mt-1 bubble ...">
        ${forwardedHTML}  <!-- 🆕 Отображается вверху сообщения -->
        ${text ? text : ''}
        ${attachmentsHTML}
      </div>
    </div>`;
}

5. Добавлены стили (backend/static/css/components/chat-detail.css)

/* Индикатор пересланного сообщения */
.forwarded-indicator {
  padding: 8px 10px;
  border-left: 3px solid currentColor;
  opacity: 0.7;
  background: rgba(0, 0, 0, 0.05);
  border-radius: 6px;
}

.bubble-me .forwarded-indicator {
  background: rgba(255, 255, 255, 0.1);
  border-left-color: rgba(255, 255, 255, 0.6);
}

/* Темная тема */
[data-bs-theme="dark"] .forwarded-indicator {
  background: rgba(255, 255, 255, 0.05);
}

[data-bs-theme="dark"] .bubble-me .forwarded-indicator {
  background: rgba(255, 255, 255, 0.08);
}

Визуальное представление

До:

┌─────────────────────────────────────┐
│ Иван Петров · 15:30                 │
│ ┌─────────────────────────────────┐ │
│ │ Привет! Как дела?               │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘

После (переслано из другого чата):

┌─────────────────────────────────────┐
│ Вы · 16:45                          │
│ ┌─────────────────────────────────┐ │
│ │ ↱ Переслано от Мария Иванова    │ │ 🆕
│ │   из «Техподдержка»              │ │ 🆕
│ │   25.11.2025 14:20               │ │ 🆕
│ │                                  │ │
│ │ Привет! Как дела?                │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘

Примеры отображения

1. Пересылка из группового чата

↱ Переслано от Алексей Сидоров
  из «Разработка»
  30.11.2025 10:15

2. Пересылка из личного чата

↱ Переслано от Елена Смирнова
  из «Елена Смирнова»
  29.11.2025 18:45

3. Пересылка из объявления

↱ Переслано от HR Отдел
  из «Важные новости»
  28.11.2025 09:00

4. Старые пересланные сообщения (без новых полей)

↱ Переслано от Дмитрий Козлов

(без даты и чата - для совместимости со старыми данными)

Миграция базы данных

# Создание миграции
python manage.py makemigrations communications --name add_forwarded_metadata

# Применение миграции
python manage.py migrate communications

Результат миграции:

ALTER TABLE communications_message 
ADD COLUMN forwarded_from_created_at DATETIME NULL;

ALTER TABLE communications_message 
ADD COLUMN forwarded_from_chat_name VARCHAR(255) NULL;

Совместимость

Обратная совместимость

✅ Старые пересланные сообщения (созданные до обновления):

  • Отображаются БЕЗ даты и названия чата
  • Показывается только автор: "Переслано от {имя}"

✅ Новые пересланные сообщения (после обновления):

  • Отображаются СО всеми метаданными
  • Показывается: автор, дата/время, чат

Получение названия чата

Групповые/Департаменты/Каналы:

source_chat_name = original_msg.chat.name or "Чат"
# Результат: "Разработка", "HR отдел", etc.

Личные чаты:

other_user = original_msg.chat.get_other_user(request.user)
source_chat_name = other_user.get_full_name() or other_user.username
# Результат: "Иван Петров", "ivanov"

Объявления:

source_chat_name = original_msg.chat.name  # Название объявления
# Результат: "Важные новости", "Обновление системы"

Тестирование

Сценарий 1: Пересылка из группового чата

  1. Откройте групповой чат "Разработка"
  2. Найдите сообщение от "Алексей Сидоров" (30.11.2025 10:15)
  3. Выделите сообщение (ПКМ → Переслать)
  4. Выберите целевой чат
  5. ✅ В целевом чате должно отображаться:
    ↱ Переслано от Алексей Сидоров
      из «Разработка»
      30.11.2025 10:15
    

Сценарий 2: Пересылка из личного чата

  1. Откройте личный чат с "Елена Смирнова"
  2. Выделите сообщение
  3. Перешлите в групповой чат
  4. ✅ Должно показать:
    ↱ Переслано от Елена Смирнова
      из «Елена Смирнова»
      [дата и время]
    

Сценарий 3: Множественная пересылка

  1. Выделите 3 сообщения
  2. Перешлите их в другой чат
  3. ✅ Все 3 сообщения должны иметь:
    • Индикатор пересылки
    • Оригинального автора
    • Дату оригинала
    • Название исходного чата

Сценарий 4: Старые пересланные сообщения

  1. Найдите старое переслаанное сообщение (до обновления)
  2. ✅ Должно отображаться только:
    ↱ Переслано от [автор]
    
    (без даты и чата)

Сценарий 5: Темная тема

  1. Переключите тему на темную
  2. Откройте чат с пересланными сообщениями
  3. ✅ Проверьте контрастность и читаемость:
    • Фон индикатора должен быть видимым
    • Текст должен быть читаемым
    • Граница слева должна быть заметной

Технические детали

Размер данных

forwarded_from_created_at:

  • Тип: DATETIME
  • Размер: 8 байт
  • Nullable: TRUE

forwarded_from_chat_name:

  • Тип: VARCHAR(255)
  • Размер: до 255 символов
  • Nullable: TRUE

Производительность

Impact: Минимальный

  • Добавлены 2 nullable поля (не влияет на существующие строки)
  • Нет индексов (не нужны для этих полей)
  • Нет JOIN запросов (данные денормализованы для скорости)

Денормализация данных

Почему сохраняем chat_name а не chat_id?

  • ✅ Быстрее (не нужен JOIN)
  • ✅ Надежнее (чат может быть удален)
  • ✅ Историчность (название могло измениться)

Почему сохраняем created_at а не ссылку на сообщение?

  • ✅ Историчность (оригинальное сообщение может быть удалено)
  • ✅ Производительность (не нужен запрос к другой таблице)

Связанные файлы

  1. backend/communications/models.py - модель Message
  2. backend/api/v1/communications/views.py - forward_messages view
  3. backend/communications/consumers.py - serialize_message функция
  4. backend/static/js/components/chatMessageTemplates.js - createMessageElement
  5. backend/static/css/components/chat-detail.css - .forwarded-indicator стили
  6. backend/communications/migrations/0018_add_forwarded_metadata.py - миграция

Дата реализации

30 ноября 2025 г.

Статус

✅ Реализовано и готово к тестированию