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