Евгений Легоцкой25 октября 2018 г. 2:47

Django - Урок 039. Добавление личных сообщений и чатов на сайте - Часть 2 (Счётчик диалогов и чатов с непрочитанными сообщениями)

Выдалось свободное время, чтобы подкорректировать личные сообщения на сайте. Данный функционал используется не особо часто, поэтому не прилагаю больших усилий к его улучшению, хотя пора уже привести данный функционал к адекватной работе.

Раньше имелась очень большая недоработка, которая заключалась в том, что не показывался счётчик диалогов с непрочитанными сообщениями, что приводило к тому, что пользователей, которому присылали сообщение, просто не обращал на него внимания, поскольку не знал об этом сообщений.

Теперь я наконец исправил этот недостаток. И в рамках предыдущего кода покажу, какие были добавлены исправления.

Я размышлял над двумя вариантами организации счётчиков непрочитанных сообщений. Вернее одним вариантов и его более продвинутой версией.

  • При каждом запросе проверять все чаты, выбирать из них последние сообщения и проверять, является ли автором авторизованный пользователь, для которого нужно проверить данное сообщение. Если он не является автором, то проверяем, прочитано ли данное сообщение, если нет, то данный диалог для данного пользователя считаем непрочитанных. Количество таких диалогов будем считать количеством непрочитанных диалогов.
  • А второй вариант, на котором я остановился предполагает ту же самую логику, только диалог или чат должен иметь внешний ключ на самое последнее сообщение, которое было в него добавлено. Данный ключ будет обновляться при каждом новом сообщении. Тогда отпадает необходимость выборки сообщений по чату и их сортировки, чтобы получить самое последнее сообщение. Что, по-моему мнению может вылиться в большие накладные расходы для базы данных, если диалогов будет очень много.

Реализация

models.py

Добавим внешний ключ на последнее сообщение, а также а также кастомный менеджер для чатов ChatManager.

# -*- coding: utf-8 -*-

#... Код из предыдущей части


class ChatManager(models.Manager):
    use_for_related_fields = True

    # Метод принимает пользователя, для которого должна производиться выборка
    # Если пользователь не добавлен, то будет возвращены все диалоги,
    # в которых хотя бы одно сообщение не прочитано
    def unreaded(self, user=None):
        qs = self.get_queryset().exclude(last_message__isnull=True).filter(last_message__is_readed=False)
        return qs.exclude(last_message__author=user) if user else qs


class Chat(models.Model):
    #... Код из предыдущей части

    # внешний ключ на последнее сообщение,
    # важный момент в том, что название класса Message пишем обычной строкой,
    # поскольку на момент чтения класса Chat интерпретатор Python ничего не знает о классе Message
    # Также необходимо добавить related_name, имя через которое будет ассоциироваться выборка данного сообщения из базы данных
    last_message = models.ForeignKey('Message', related_name='last_message', null=True, blank=True, on_delete=models.SET_NULL)

    objects = ChatManager()

    @models.permalink
    def get_absolute_url(self):
        return 'users:messages', (), {'chat_id': self.pk }


class Message(models.Model):
    #... Код из предыдущей части

receivers.py

Я впервые пишу а таком файле Python в проекте на Django. Суть его в том, что там будут объявлены обработчики сигналов от модели. Дело в том, что в Django при сохранении объекта модели испускаются некоторые сигналы, которые можно обрабатывать. Это позволяет некоторую единообразную логику поместить в отдельный файл и сделать остальной код проекта несколько чище. Минусом является то, что данный код может быть неочевидным, поскольку никаких ссылок на данный код в других частях проекта не будет.

# -*- coding: utf-8 -*-

from django.db.models.signals import post_save
from django.dispatch import receiver

from users.models import Message


# обработчик сохранения объекта сообщения
@receiver(post_save, sender=Message)
def post_save_comment(sender, instance, created, **kwargs):
    # если объект был создан
    if created:
        # указываем чату, в котором находится данное сообщение, что это последнее сообщение
        instance.chat.last_message = instance
        # и обновляем данный внешний ключ чата
        instance.chat.save(update_fields=['last_message'])

Но просто так данный код не заработает, поскольку данный файл тоже нужно загрузить в интерпретатор.

Сделать это можно в файле apps.py при инициализации приложения

apps.py

Делается это в методе ready

# -*- coding: utf-8 -*-

from django.apps import AppConfig
from django.utils.translation import ugettext_lazy as _


class UsersConfig(AppConfig):
    name = 'users'
    verbose_name = _('Users')

    def ready(self):
        import users.receivers

Применение

Теперь, когда у вас есть всё необходимое для получения количества диалогов с непрочитанными сообщениями, вы можете добавить данную информацию в контекст для рендеринга шаблона.

context['unreaded_dialogs_counter'] = user.chat_set.unreaded(user=user).count()

Как исправить старые диалоги

Осталось только исправить старые диалоги, в которых уже есть сообщения. Сделать это можно будет через админку, если добавить соответствующее действие.

admin.py

# -*- coding: utf-8 -*-

from django.contrib import admin

from users import models


class ChatAdmin(admin.ModelAdmin):
    autocomplete_fields = ['members']
    search_fields = ('members',)
    actions = ['fix_last_messages']

    def fix_last_messages(self, request, queryset):
        for chat in queryset.all():
            chat.last_message = chat.message_set.all().order_by('-pub_date').first()
            chat.save(update_fields=['last_message'])

    fix_last_messages.short_description = "Fix last messages"


class MessageAdmin(admin.ModelAdmin):
    autocomplete_fields = ['chat', 'author']
    list_display = ('chat', 'author', 'message', 'pub_date', 'is_readed')


admin.site.register(models.Chat, ChatAdmin)
admin.site.register(models.Message, MessageAdmin)
Рекомендуем хостинг TIMEWEB
Рекомендуем хостинг TIMEWEB
Стабильный хостинг, на котором располагается социальная сеть EVILEG. Для проектов на Django рекомендуем VDS хостинг.
Поддержать автора Donate
  • #
  • 4 августа 2020 г. 2:19
  • (ред.)

Здравствуйте, подскажите как именно должна выглядеть уже готовая вьюха с context? Не догоняю как его вставить

  • #
  • 4 августа 2020 г. 2:25

Может быть посоветуете как добавить необязательное поле + прокинуть его во вьюху что бы можно было отправлять небольшие документы.?

Этот вопрос я решил)

Добавляйте поле файла в модель сообщения. И в форме сообщения указывайте, что поле с файлом.

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Timeweb

Позвольте мне порекомендовать вам отличный хостинг, на котором расположен EVILEG.

В течение многих лет Timeweb доказывает свою стабильность.

Для проектов на Django рекомендую VDS хостинг

Посмотреть Хостинг
k
  • knobu
  • 23 сентября 2020 г. 2:34

C++ - Тест 006. Перечисления

  • Результат:60баллов,
  • Очки рейтинга-1
k
  • knobu
  • 23 сентября 2020 г. 2:21

C++ - Тест 005. Структуры и Классы

  • Результат:91баллов,
  • Очки рейтинга8
k
  • knobu
  • 23 сентября 2020 г. 2:16

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80баллов,
  • Очки рейтинга4
Последние комментарии

Qt/C++ - Урок 006. QSqlQueryModel - Таблицы в Qt с помощью SQL-запросов

QSqlTableModel выполняет ряд стандартных операций для одной таблицы из базы данных. Поэтому там и реализован функционал по удалению и редактированию. QSqlQueryModel позволяет выполнить запр…
VB

Qt/C++ - Урок 006. QSqlQueryModel - Таблицы в Qt с помощью SQL-запросов

Добрый день. Хотел спросить вот что. Создал проект на основе QAbstractTableModel. В MainWindow cоответственно создал модель и связал с представлением. Поиск веду по списку элементов модели,…

QCheckBox в качестве делегата QTableView

До тех пор, пока у вас проект содержит только одну таблицу, или несколько то может быть. Когда их будет 1000 и чекбоксы в разных колонках, то без делегатов и переопределения возвращаемых ре…
D
  • Damir
  • 20 сентября 2020 г. 15:34

QCheckBox в качестве делегата QTableView

bool Node::setData(const QModelIndex& index, const QVariant& value, int role){ switch (index.column()) { case 0: switch (role) { case Qt::CheckStateRole:// <- т…
VB

Qt/C++ - Урок 004. QSqlTableModel или Как представить таблицу из БД в Qt?

Почему-то такой метод для обновления не работает, который можно было бы применить в данном примере. То есть в представлении данные удаляются и обновляются, а в базе данных изменений не происходи…
Сейчас обсуждают на форуме

Как в Qt в qmenu добавить scrollarea

Вот это наследованный класс меню. Но посути это обычное меню. #pragma once#include <QtWidgets>class TransMenu : public QMenu { Q_OBJECTpublic: TransMenu(QWidget* parent = …

Как в qml работать с динамически созданными потомками?

В QML есть сборщик мусора, он может удалять объекты не сразу а по ппрошествии времени. Попробуйте при удалении вызывать сборщик мусора принудительно через gc()
VB

Как запустить программу с базой данных PostgreSQL на другом компьютере

Не требует никакую библиотеку, запускается на других компьютерах, где не установлена PostgreSQL, но создать элемент невозможно, тем более отредактировать или удалить.
p
  • prod1s
  • 24 сентября 2020 г. 7:12

через QT не могу открыть файл SQLite

Вирішення знайшов. Вказав замість назви БД об'єкт класу QSqlDataBase для QSqlQuery. QSqlQuery m_query = QSqlQuery(qSqlDataBase); Після двох днів пошуку рішення, все-таки знайшов…
U

как скрыть елемент с копии виджета

Дело в том, что ui класса находится в private-секции... И из-вне доступ получить, не нарушая канонов - не получится) Можно конечно сделать что-то в духе #define private public, но это для истинн…
О нас
Услуги
© EVILEG 2015-2020
Рекомендует хостинг TIMEWEB