from django.db import models
from django.utils import timezone
[docs]
class Message(models.Model):
"""
Represents a single chat message exchanged between a matched mentor and mentee.
Uses email addresses as identifiers to stay consistent with the matching
layer (``matchedMentorEmail``, ``current_mentees``). Messages are ordered
by ``timestamp`` ascending by default, and the composite indexes on
``(sender_email, receiver_email)`` and ``(receiver_email, is_read)``
support efficient conversation retrieval and unread-count queries
respectively.
Attributes:
sender_email (EmailField): Email address of the user who sent the message.
receiver_email (EmailField): Email address of the intended recipient.
content (TextField): Plain-text body of the message.
timestamp (DateTimeField): When the message was created. Defaults to
the current time at the moment of instantiation.
is_read (BooleanField): Whether the recipient has read the message.
Defaults to ``False``.
"""
sender_email = models.EmailField(max_length=255, db_index=True)
receiver_email = models.EmailField(max_length=255, db_index=True)
content = models.TextField()
timestamp = models.DateTimeField(default=timezone.now, db_index=True)
is_read = models.BooleanField(default=False)
class Meta:
db_table = 'chat_messages'
ordering = ['timestamp']
indexes = [
models.Index(fields=['sender_email', 'receiver_email']),
models.Index(fields=['receiver_email', 'is_read']),
]
def __str__(self):
"""
Return a human-readable summary of the message.
Truncates ``content`` to 50 characters to keep the representation concise.
:returns: A string in the form ``'<sender> -> <receiver>: <content[:50]>'``.
:rtype: str
"""
return f"{self.sender_email} -> {self.receiver_email}: {self.content[:50]}"
[docs]
@classmethod
def get_conversation(cls, email1, email2, limit=50, offset=0):
"""
Retrieve paginated messages exchanged between two users.
Returns messages in descending timestamp order (newest first) so that
pagination offsets naturally surface the most recent content first.
Both directions of the conversation (``email1 → email2`` and
``email2 → email1``) are included.
:param email1: Email address of one participant in the conversation.
:type email1: str
:param email2: Email address of the other participant in the conversation.
:type email2: str
:param limit: Maximum number of messages to return. Defaults to ``50``.
:type limit: int
:param offset: Number of messages to skip before returning results,
used for pagination. Defaults to ``0``.
:type offset: int
:returns: Queryset of :class:`Message` instances ordered newest-first,
sliced to ``[offset : offset + limit]``.
:rtype: django.db.models.QuerySet
Example::
>>> page1 = Message.get_conversation('mentee@g.ucla.edu', 'mentor@ucla.edu')
>>> page2 = Message.get_conversation('mentee@g.ucla.edu', 'mentor@ucla.edu', offset=50)
"""
return cls.objects.filter(
models.Q(sender_email=email1, receiver_email=email2) |
models.Q(sender_email=email2, receiver_email=email1)
).order_by('-timestamp')[offset:offset + limit]
[docs]
@classmethod
def get_unread_count(cls, receiver_email, sender_email=None):
"""
Count unread messages for a given recipient.
When ``sender_email`` is provided the count is scoped to messages from
that specific sender only, which is useful for per-conversation unread
badges. When omitted, all unread messages across every sender are counted.
:param receiver_email: Email address of the recipient whose unread
messages are being counted.
:type receiver_email: str
:param sender_email: If supplied, restrict the count to messages from
this sender. Defaults to ``None`` (count across all senders).
:type sender_email: str, optional
:returns: Total number of unread messages matching the query.
:rtype: int
Example::
>>> Message.get_unread_count('mentee@g.ucla.edu')
7
>>> Message.get_unread_count('mentee@g.ucla.edu', sender_email='mentor@ucla.edu')
3
"""
query = cls.objects.filter(
receiver_email=receiver_email,
is_read=False
)
if sender_email:
query = query.filter(sender_email=sender_email)
return query.count()
[docs]
@classmethod
def mark_as_read(cls, receiver_email, sender_email):
"""
Mark all unread messages from a specific sender as read.
Issues a single bulk ``UPDATE`` query rather than loading and saving
individual instances, making it efficient for conversations with a
large number of unread messages.
:param receiver_email: Email address of the recipient for whom messages
are being marked as read.
:type receiver_email: str
:param sender_email: Email address of the sender whose messages should
be marked as read.
:type sender_email: str
:returns: The number of ``Message`` rows updated.
:rtype: int
Example::
>>> updated = Message.mark_as_read('mentee@g.ucla.edu', 'mentor@ucla.edu')
>>> print(f'{updated} messages marked as read.')
"""
return cls.objects.filter(
sender_email=sender_email,
receiver_email=receiver_email,
is_read=False
).update(is_read=True)