Django: группировать по двум столбцам и различать их без учета порядка

У меня есть модель TextMessage, как вы видите:

class TextMessaage(models.Model):

    id = models.UUIDField(
        _("Id"),
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        null=False,
        blank=False,
    )

    created_at = models.DateTimeField(_("Created at"), auto_now_add=True)
    modified_at = models.DateTimeField(_("Modified at"), auto_now=True)

    sender = models.ForeignKey(
        User,
        on_delete=models.PROTECT,
        related_name="sent_messages",
        blank=False,
        null=False,
        verbose_name=_("Sender"),
    )

    receiver = models.ForeignKey(
        User,
        on_delete=models.PROTECT,
        related_name="received_messages",
        blank=False,
        null=False,
        verbose_name=_("Receiver"),
    )

    body = models.TextField(
        null=False,
        blank=False,
        validators=[MaxLengthValidator(4096)],
        verbose_name=_("Body"),
    )

Я пытаюсь получить последнее сообщение из каждого разговора. Я пробовал следующее:

conversations = (
            models.TextMessage.objects.filter(Q(sender=user) | Q(receiver=user))
            .order_by(
                "sender__id",
                "receiver_id",
                "-modified_at",
            )
            .distinct("sender__id", "receiver_id")
        ).all()

Проблема в том, что запрос, который я написал, будет считать пару (отправитель=A, получатель=B) отличной от (отправитель=B, получатель=A), а это не то, что я ищу. Я хочу, чтобы эти две пары считались одинаковыми, поскольку у них одинаковые внешние ключи, независимо от их порядка. Что мне делать?

Для дополнительной информации, я использую Postgres, кстати.

Вы можете добиться этого, используя @property (свойство модели), например, так

class TextMessaage(models.Model):

    id = models.UUIDField(
        _("Id"),
        primary_key=True,
        default=uuid.uuid4,
        editable=False,
        null=False,
        blank=False,
    )

    created_at = models.DateTimeField(_("Created at"), auto_now_add=True)
    modified_at = models.DateTimeField(_("Modified at"), auto_now=True)

    sender = models.ForeignKey(
        User,
        on_delete=models.PROTECT,
        related_name="sent_messages",
        blank=False,
        null=False,
        verbose_name=_("Sender"),
    )

    receiver = models.ForeignKey(
        User,
        on_delete=models.PROTECT,
        related_name="received_messages",
        blank=False,
        null=False,
        verbose_name=_("Receiver"),
    )

    body = models.TextField(
        null=False,
        blank=False,
        validators=[MaxLengthValidator(4096)],
        verbose_name=_("Body"),
    )

    @property 
    def last_msg(self):
        last_m = TextMessaage.objects.filter(receiver=self.receiver).order_by('-created_at').first()
        return last_m
    


all_msg = TextMessaage.objects.all() # get all conversations 
for m in all_msg:
    print(m.sender.username)
    print(m.receiver.username)
    print(m.body)
    print(m.created_at)
    print(m.last_m) # This will print the last message sent to the user
    print("************")

Я решил эту проблему следующим образом:

def get_last_messages_for_user(user: User):
    """Get last message of all conversations that a user is involved."""
    messages_related_to_user = (
        models.TextMessage.objects.filter(Q(sender=user) | Q(receiver=user))
        .order_by(
            "sender__id",
            "receiver__id",
            "-modified_at",
        )
        .distinct(
            "sender__id",
            "receiver__id",
        )
    )

    # NOTE: I needed a fresh copy previous queryset to do ordering
    # Django does not allow for ordering
    # Also, I have no idea that how to get a fresh copy of a queryset
    # TODO: find a better solution
    messages_related_to_user = sorted(
        messages_related_to_user, key=lambda m: m.modified_at, reverse=True
    )

    # Manually distinguishing `sender__id` and `receiver__id` regardless of order
    last_messages = []
    checked_users = set()

    for message in messages_related_to_user:
        print(message.sender.username, message.body)
        if not (
            message.sender.username in checked_users
            and message.receiver.username in checked_users
        ):
            last_messages.append(
                {
                    "body": message.body,
                    "modified_at": message.modified_at,
                    "user": (
                        message.receiver.username
                        if message.sender == user
                        else message.sender.get_username
                    ),
                }
            )
            checked_users.add(message.sender.username)
            checked_users.add(message.receiver.username)

    return last_messages

Вернуться на верх