import logging
from django.conf import settings
from django.contrib.auth.models import User
from django.core.validators import MaxLengthValidator
from django.db import models
from django.db.models import Q
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.urls import reverse
from django.utils import timezone
from django.utils.text import Truncator
from django.utils.translation import gettext_lazy as _
from oioioi.base.fields import EnumField, EnumRegistry
from oioioi.base.utils.validators import validate_whitespaces
from oioioi.contests.models import Contest, ProblemInstance, Round
[docs]message_kinds = EnumRegistry()
message_kinds.register('QUESTION', _("Question"))
message_kinds.register('PRIVATE', _("Private message"))
message_kinds.register('PUBLIC', _("Public message"))
[docs]message_kind_labels = EnumRegistry()
message_kind_labels.register('QUESTION', _("QUESTION"))
message_kind_labels.register('PRIVATE', _("PRIVATE"))
message_kind_labels.register('PUBLIC', _("PUBLIC"))
[docs]logger = logging.getLogger('oioioi')
[docs]class Message(models.Model):
[docs] contest = models.ForeignKey(
Contest, null=True, blank=True, on_delete=models.CASCADE
)
[docs] round = models.ForeignKey(Round, null=True, blank=True, on_delete=models.CASCADE)
[docs] problem_instance = models.ForeignKey(
ProblemInstance, null=True, blank=True, on_delete=models.CASCADE
)
[docs] top_reference = models.ForeignKey(
'self', null=True, blank=True, on_delete=models.CASCADE
)
[docs] author = models.ForeignKey(User, on_delete=models.CASCADE)
[docs] kind = EnumField(message_kinds, default='QUESTION', verbose_name=_("kind"))
[docs] topic = models.CharField(
max_length=255,
verbose_name=_("topic"),
validators=[MaxLengthValidator(255), validate_whitespaces],
)
[docs] content = models.TextField(verbose_name=_("content"))
[docs] date = models.DateTimeField(
default=timezone.now, editable=False, verbose_name=_("date")
)
[docs] pub_date = models.DateTimeField(
default=None, blank=True, null=True, verbose_name=_("publication date")
)
[docs] marked_read_by = models.ForeignKey(
User,
blank=True,
null=True,
on_delete=models.SET_NULL,
# This is needed, because we have another ForeignKey to User - author
related_name='message_marked_read_set',
verbose_name=_("marked read by")
)
[docs] mail_sent = models.BooleanField(
default=False, verbose_name=_("mail notification sent")
)
[docs] def save(self, *args, **kwargs):
# Assert integrity in this Message
if not self._has_category():
assert self.top_reference and self.top_reference._has_category()
self.problem_instance = self.top_reference.problem_instance
self.round = self.top_reference.round
elif self.problem_instance:
self.round = self.problem_instance.round
self.contest = self.round.contest
# Propagate to all related Messages
if self.top_reference:
related = Message.objects.filter(
Q(id=self.top_reference_id) | Q(top_reference_id=self.top_reference_id)
)
elif self.id:
related = self.message_set.all()
else:
related = Message.objects.none()
if self.id:
related.exclude(id=self.id)
related.update(
round=self.round,
contest=self.contest,
problem_instance=self.problem_instance,
)
super(Message, self).save(*args, **kwargs)
[docs] def can_have_replies(self):
return self.kind == 'QUESTION'
[docs] def _has_category(self):
return self.round is not None or self.problem_instance is not None
[docs] def __str__(self):
return u'%s - %s' % (message_kinds.get(self.kind, self.kind), self.topic)
@property
[docs] def to_quote(self):
lines = self.content.strip().split('\n')
return ''.join('> ' + l for l in lines)
[docs] def get_absolute_url(self):
link = reverse(
'message',
kwargs={
'contest_id': self.contest.id,
'message_id': self.top_reference_id
if self.top_reference_id is not None
else self.id,
},
)
return link
[docs] def get_user_date(self):
""" returns date visible by a user """
return self.pub_date if self.pub_date is not None else self.date
[docs] def get_kind_label(self):
return message_kind_labels[self.kind]
[docs]class ReplyTemplate(models.Model):
[docs] contest = models.ForeignKey(
Contest, null=True, blank=True, on_delete=models.CASCADE
)
[docs] name = models.CharField(max_length=255, verbose_name=_("visible name"), blank=True)
[docs] content = models.TextField(verbose_name=_("content"))
# Incremented every time admin includes this template in a reply.
[docs] usage_count = models.IntegerField(verbose_name=_("usage count"), default=0)
[docs] def __str__(self):
return u'%s: %s' % (self.visible_name, self.content)
@property
[docs] def visible_name(self):
if self.name:
return self.name
length = getattr(settings, 'REPLY_TEMPLATE_VISIBLE_NAME_LENGTH', 15)
return Truncator(self.content).chars(length)
[docs]class MessageView(models.Model):
[docs] message = models.ForeignKey(Message, on_delete=models.CASCADE)
[docs] user = models.ForeignKey(User, on_delete=models.CASCADE)
[docs] date = models.DateTimeField(default=timezone.now, editable=False)
[docs]class MessageNotifierConfig(models.Model):
[docs] contest = models.ForeignKey(Contest, on_delete=models.CASCADE)
[docs] user = models.ForeignKey(User, verbose_name=_("username"), on_delete=models.CASCADE)
@receiver(post_save, sender=Message)
[docs]def send_notification(sender, instance, created, **kwargs):
# Don't send a notification when the message was just edited
if not created:
return
# Send a notification if this is a new public message
if instance.kind == 'PUBLIC' and instance.contest is not None:
if instance.problem_instance is not None:
logger.info(
"Public message \"%(topic)s\""
" about problem \"%(short_name)s\" was created",
{
'topic': instance.topic,
'short_name': instance.problem_instance.short_name,
},
extra={
'notification': 'new_public_message',
'message_instance': instance,
'contest': instance.contest,
},
)
else:
logger.info(
"Public message \"%(topic)s\" was created",
{'topic': instance.topic},
extra={
'notification': 'new_public_message',
'message_instance': instance,
'contest': instance.contest,
},
)
# Send a notification if this is a new answer for question
elif instance.top_reference is not None:
if instance.problem_instance is not None:
logger.info(
"Answer for question \"%(topic)s\""
" about problem \"%(short_name)s\" was sent",
{
'topic': instance.topic,
'short_name': instance.top_reference.problem_instance.short_name,
},
extra={
'notification': 'question_answered',
'question_instance': instance.top_reference,
'answer_instance': instance,
'user': instance.top_reference.author,
},
)
else:
logger.info(
"Answer for question \"%(topic)s\" was sent",
{'topic': instance.topic},
extra={
'notification': 'question_answered',
'question_instance': instance.top_reference,
'answer_instance': instance,
'user': instance.top_reference.author,
},
)
# an e-mail notification will be spawned for every post
# with Message.top_reference == EmailSubscription.opening_post
[docs]class QuestionSubscription(models.Model):
[docs] user = models.ForeignKey(User, on_delete=models.CASCADE)
[docs] contest = models.ForeignKey(Contest, on_delete=models.CASCADE)