Add notification on ticket messages

This commit is contained in:
cuom1999 2020-10-19 22:54:13 -05:00
parent aa43b26683
commit 7065d9ce6b
8 changed files with 90 additions and 10 deletions

View file

@ -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'),
),
]

View file

@ -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'),
),
]

View file

@ -189,6 +189,8 @@ class CommentLock(models.Model):
class Notification(models.Model): class Notification(models.Model):
owner = models.ForeignKey(Profile, verbose_name=_('owner'), related_name="notifications", on_delete=CASCADE) owner = models.ForeignKey(Profile, verbose_name=_('owner'), related_name="notifications", on_delete=CASCADE)
time = models.DateTimeField(verbose_name=_('posted time'), auto_now_add=True) 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) read = models.BooleanField(verbose_name=_('read'), default=False)
category = models.CharField(verbose_name=_('category'), max_length=10) 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)

View file

@ -129,7 +129,6 @@ class Profile(models.Model):
def count_unseen_notifications(self): def count_unseen_notifications(self):
query = { query = {
'read': False, 'read': False,
'comment__hidden': False,
} }
return self.notifications.filter(**query).count() return self.notifications.filter(**query).count()

View file

@ -19,8 +19,8 @@ class NotificationList(ListView):
query = { query = {
'owner': self.request.profile, 'owner': self.request.profile,
'comment__hidden': False,
} }
self.queryset = Notification.objects.filter(**query)\ self.queryset = Notification.objects.filter(**query)\
.order_by('-time')[:100] \ .order_by('-time')[:100] \
.annotate(seen=Value(True, output_field=BooleanField())) .annotate(seen=Value(True, output_field=BooleanField()))

View file

@ -1,5 +1,7 @@
import json import json
from itertools import chain
from django import forms from django import forms
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import ImproperlyConfigured, PermissionDenied, ValidationError 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 django.views.generic.detail import SingleObjectMixin
from judge import event_poster as event 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.diggpaginator import DiggPaginator
from judge.utils.tickets import filter_visible_tickets, own_ticket_filter from judge.utils.tickets import filter_visible_tickets, own_ticket_filter
from judge.utils.views import SingleObjectFormView, TitleMixin, paginate_query_context 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)) preview_timeout=1000, hide_preview_button=True))
def add_ticket_notifications(users, author, link, ticket):
html = f"<a href=\"{link}\">{ticket.linked_item}</a>"
for user in users:
notification = Notification(owner=user,
html_link=html,
category='Ticket',
author=author)
notification.save()
class TicketForm(forms.Form): class TicketForm(forms.Form):
title = forms.CharField(max_length=100, label=gettext_lazy('Ticket title')) title = forms.CharField(max_length=100, label=gettext_lazy('Ticket title'))
body = forms.CharField(widget=ticket_widget) 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 = TicketMessage(ticket=ticket, user=ticket.user, body=form.cleaned_data['body'])
message.save() message.save()
ticket.assignees.set(self.get_assignees()) 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: if event.real:
event.post('tickets', { event.post('tickets', {
'type': 'new-ticket', 'id': ticket.id, 'type': 'new-ticket', 'id': ticket.id,
'message': message.id, 'user': ticket.user_id, 'message': message.id, 'user': ticket.user_id,
'assignees': list(ticket.assignees.values_list('id', flat=True)), 'assignees': list(ticket.assignees.values_list('id', flat=True)),
}) })
return HttpResponseRedirect(reverse('ticket', args=[ticket.id])) return HttpResponseRedirect(link)
class NewProblemTicketView(ProblemMixin, TitleMixin, NewTicketView): class NewProblemTicketView(ProblemMixin, TitleMixin, NewTicketView):
@ -127,6 +144,13 @@ class TicketView(TitleMixin, LoginRequiredMixin, TicketMixin, SingleObjectFormVi
body=form.cleaned_data['body'], body=form.cleaned_data['body'],
ticket=self.object) ticket=self.object)
message.save() 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: if event.real:
event.post('tickets', { event.post('tickets', {
'type': 'ticket-message', 'id': self.object.id, 'type': 'ticket-message', 'id': self.object.id,
@ -136,7 +160,7 @@ class TicketView(TitleMixin, LoginRequiredMixin, TicketMixin, SingleObjectFormVi
event.post('ticket-%d' % self.object.id, { event.post('ticket-%d' % self.object.id, {
'type': 'ticket-message', 'message': message.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): def get_title(self):
return _('%(title)s - Ticket %(id)d') % {'title': self.object.title, 'id': self.object.id} return _('%(title)s - Ticket %(id)d') % {'title': self.object.title, 'id': self.object.id}

View file

@ -214,6 +214,7 @@
<a href="{{ ticket.linked_item.get_absolute_url() }}"> <a href="{{ ticket.linked_item.get_absolute_url() }}">
{{ ticket.linked_item|item_title }}</a> {{ ticket.linked_item|item_title }}</a>
</div> </div>
<div>{{ link_user(ticket.user) }}</div>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>

View file

@ -11,19 +11,30 @@
<tr> <tr>
<th>{{ _('User') }}</th> <th>{{ _('User') }}</th>
<th>{{ _('Activity') }}</th> <th>{{ _('Activity') }}</th>
<th>{{ _('Comment') }}</th> <th>{{ _('Link') }}</th>
<th>{{ _('Time') }}</th> <th>{{ _('Time') }}</th>
</tr> </tr>
{% for notification in notifications %} {% for notification in notifications %}
<tr class="{{ 'highlight' if not notification.seen }}"> <tr class="{{ 'highlight' if not notification.seen }}">
<td> <td>
{% if notification.comment %}
{{ link_user(notification.comment.author) }} {{ link_user(notification.comment.author) }}
{% else %}
{{ link_user(notification.author) }}
{% endif %}
</td> </td>
<td> <td>
{{ notification.category }} {{ notification.category }}
</td> </td>
<td> <td>
{% if notification.comment %}
<a href="{{ notification.comment.link }}#comment-{{ notification.comment.id }}">{{ page_titles[notification.comment.page] }}</a> <a href="{{ notification.comment.link }}#comment-{{ notification.comment.id }}">{{ page_titles[notification.comment.page] }}</a>
{% else %}
{% autoescape off %}
{{notification.html_link}}
{% endautoescape %}
{% endif %}
</td> </td>
<td> <td>
{{ relative_time(notification.time) }} {{ relative_time(notification.time) }}