diff --git a/judge/migrations/0109_auto_20201017_1151.py b/judge/migrations/0109_auto_20201017_1151.py new file mode 100644 index 0000000..9bce543 --- /dev/null +++ b/judge/migrations/0109_auto_20201017_1151.py @@ -0,0 +1,24 @@ +# Generated by Django 2.2.12 on 2020-10-17 04:51 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0108_submission_judged_date'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='html_link', + field=models.TextField(default='', max_length=1000, verbose_name='html link to comments, used for non-comments'), + ), + migrations.AlterField( + model_name='notification', + name='comment', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='judge.Comment', verbose_name='comment'), + ), + ] diff --git a/judge/migrations/0110_notification_author.py b/judge/migrations/0110_notification_author.py new file mode 100644 index 0000000..e0dec3e --- /dev/null +++ b/judge/migrations/0110_notification_author.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.12 on 2020-10-17 05:10 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('judge', '0109_auto_20201017_1151'), + ] + + operations = [ + migrations.AddField( + model_name='notification', + name='author', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='judge.Profile', verbose_name='who trigger, used for non-comment'), + ), + ] diff --git a/judge/models/comment.py b/judge/models/comment.py index ff3879c..d2d9688 100644 --- a/judge/models/comment.py +++ b/judge/models/comment.py @@ -189,6 +189,8 @@ class CommentLock(models.Model): class Notification(models.Model): owner = models.ForeignKey(Profile, verbose_name=_('owner'), related_name="notifications", on_delete=CASCADE) time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True) - comment = models.ForeignKey(Comment, verbose_name=_('comment'), on_delete=CASCADE) + comment = models.ForeignKey(Comment, null=True, verbose_name=_('comment'), on_delete=CASCADE) read = models.BooleanField(verbose_name=_('read'), default=False) - category = models.CharField(verbose_name=_('category'), max_length=10) \ No newline at end of file + category = models.CharField(verbose_name=_('category'), max_length=10) + html_link = models.TextField(default='', verbose_name=_('html link to comments, used for non-comments'), max_length=1000) + author = models.ForeignKey(Profile, null=True, verbose_name=_('who trigger, used for non-comment'), on_delete=CASCADE) \ No newline at end of file diff --git a/judge/models/profile.py b/judge/models/profile.py index d73ed70..2fb51a3 100644 --- a/judge/models/profile.py +++ b/judge/models/profile.py @@ -129,7 +129,6 @@ class Profile(models.Model): def count_unseen_notifications(self): query = { 'read': False, - 'comment__hidden': False, } return self.notifications.filter(**query).count() diff --git a/judge/views/notification.py b/judge/views/notification.py index c3540e5..b0d350f 100644 --- a/judge/views/notification.py +++ b/judge/views/notification.py @@ -19,8 +19,8 @@ class NotificationList(ListView): query = { 'owner': self.request.profile, - 'comment__hidden': False, } + self.queryset = Notification.objects.filter(**query)\ .order_by('-time')[:100] \ .annotate(seen=Value(True, output_field=BooleanField())) diff --git a/judge/views/ticket.py b/judge/views/ticket.py index 85538b4..97af320 100644 --- a/judge/views/ticket.py +++ b/judge/views/ticket.py @@ -1,5 +1,7 @@ import json +from itertools import chain + from django import forms from django.contrib.auth.mixins import LoginRequiredMixin from django.core.exceptions import ImproperlyConfigured, PermissionDenied, ValidationError @@ -17,7 +19,7 @@ from django.views.generic import ListView from django.views.generic.detail import SingleObjectMixin from judge import event_poster as event -from judge.models import Problem, Profile, Ticket, TicketMessage +from judge.models import Problem, Profile, Ticket, TicketMessage, Notification from judge.utils.diggpaginator import DiggPaginator from judge.utils.tickets import filter_visible_tickets, own_ticket_filter from judge.utils.views import SingleObjectFormView, TitleMixin, paginate_query_context @@ -29,6 +31,16 @@ ticket_widget = (forms.Textarea() if HeavyPreviewPageDownWidget is None else preview_timeout=1000, hide_preview_button=True)) +def add_ticket_notifications(users, author, link, ticket): + html = f"{ticket.linked_item}" + for user in users: + notification = Notification(owner=user, + html_link=html, + category='Ticket', + author=author) + notification.save() + + class TicketForm(forms.Form): title = forms.CharField(max_length=100, label=gettext_lazy('Ticket title')) body = forms.CharField(widget=ticket_widget) @@ -66,13 +78,18 @@ class NewTicketView(LoginRequiredMixin, SingleObjectFormView): message = TicketMessage(ticket=ticket, user=ticket.user, body=form.cleaned_data['body']) message.save() ticket.assignees.set(self.get_assignees()) + + link = reverse('ticket', args=[ticket.id]) + + add_ticket_notifications(ticket.assignees.all(), ticket.user, link, ticket) + if event.real: event.post('tickets', { 'type': 'new-ticket', 'id': ticket.id, 'message': message.id, 'user': ticket.user_id, 'assignees': list(ticket.assignees.values_list('id', flat=True)), }) - return HttpResponseRedirect(reverse('ticket', args=[ticket.id])) + return HttpResponseRedirect(link) class NewProblemTicketView(ProblemMixin, TitleMixin, NewTicketView): @@ -127,6 +144,13 @@ class TicketView(TitleMixin, LoginRequiredMixin, TicketMixin, SingleObjectFormVi body=form.cleaned_data['body'], ticket=self.object) message.save() + + link = '%s#message-%d' % (reverse('ticket', args=[self.object.id]), message.id) + + notify_list = list(chain(self.object.assignees.all(), [self.object.user])) + notify_list.remove(message.user) + add_ticket_notifications(notify_list, message.user, link, self.object) + if event.real: event.post('tickets', { 'type': 'ticket-message', 'id': self.object.id, @@ -136,7 +160,7 @@ class TicketView(TitleMixin, LoginRequiredMixin, TicketMixin, SingleObjectFormVi event.post('ticket-%d' % self.object.id, { 'type': 'ticket-message', 'message': message.id, }) - return HttpResponseRedirect('%s#message-%d' % (reverse('ticket', args=[self.object.id]), message.id)) + return HttpResponseRedirect(link) def get_title(self): return _('%(title)s - Ticket %(id)d') % {'title': self.object.title, 'id': self.object.id} diff --git a/templates/blog/list.html b/templates/blog/list.html index 4d228af..ed3c62c 100644 --- a/templates/blog/list.html +++ b/templates/blog/list.html @@ -214,6 +214,7 @@ {{ ticket.linked_item|item_title }} +
{{ link_user(ticket.user) }}
{% endfor %} diff --git a/templates/notification/list.html b/templates/notification/list.html index 176d04f..3900bb3 100644 --- a/templates/notification/list.html +++ b/templates/notification/list.html @@ -11,19 +11,30 @@ {{ _('User') }} {{ _('Activity') }} - {{ _('Comment') }} + {{ _('Link') }} {{ _('Time') }} {% for notification in notifications %} - {{ link_user(notification.comment.author) }} + {% if notification.comment %} + {{ link_user(notification.comment.author) }} + {% else %} + {{ link_user(notification.author) }} + {% endif %} + {{ notification.category }} - {{ page_titles[notification.comment.page] }} + {% if notification.comment %} + {{ page_titles[notification.comment.page] }} + {% else %} + {% autoescape off %} + {{notification.html_link}} + {% endautoescape %} + {% endif %} {{ relative_time(notification.time) }}