8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-12-23 15:33:45 +00:00

Add comment to tikets + send_notif mails

This commit is contained in:
Gabriel Detraz 2020-04-23 02:42:18 +02:00
parent c8c89fab1d
commit 38b119bb8e
15 changed files with 524 additions and 76 deletions

View file

@ -25,13 +25,15 @@ Ticket preferences model
from django.contrib import admin from django.contrib import admin
from .models import Ticket from .models import Ticket, CommentTicket
from reversion.admin import VersionAdmin from reversion.admin import VersionAdmin
class TicketAdmin(VersionAdmin): class TicketAdmin(VersionAdmin):
"""Gestion des ticket""" pass
class CommentTicketAdmin(VersionAdmin):
pass pass
admin.site.register(Ticket, TicketAdmin) admin.site.register(Ticket, TicketAdmin)
admin.site.register(CommentTicket, CommentTicketAdmin)

View file

@ -31,7 +31,7 @@ from re2o.field_permissions import FieldPermissionFormMixin
from re2o.mixins import FormRevMixin from re2o.mixins import FormRevMixin
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from .models import Ticket from .models import Ticket, CommentTicket
class NewTicketForm(FormRevMixin, ModelForm): class NewTicketForm(FormRevMixin, ModelForm):
@ -48,7 +48,7 @@ class NewTicketForm(FormRevMixin, ModelForm):
self.fields.pop('email') self.fields.pop('email')
self.instance.user = request.user self.instance.user = request.user
self.fields['description'].help_text = render_to_string('tickets/help_text.html') self.fields['description'].help_text = render_to_string('tickets/help_text.html')
self.instance.request = request self.instance.language = getattr(request, "LANGUAGE_CODE", "en")
class EditTicketForm(FormRevMixin, ModelForm): class EditTicketForm(FormRevMixin, ModelForm):
@ -62,3 +62,16 @@ class EditTicketForm(FormRevMixin, ModelForm):
super(EditTicketForm, self).__init__(*args, **kwargs) super(EditTicketForm, self).__init__(*args, **kwargs)
self.fields['email'].required = False self.fields['email'].required = False
class CommentTicketForm(FormRevMixin, ModelForm):
"""Edit and create comment to a ticket"""
class Meta:
model = CommentTicket
fields = ["comment"]
def __init__(self, *args, **kwargs):
prefix = kwargs.pop("prefix", self.Meta.model.__name__)
super(CommentTicketForm, self).__init__(*args, prefix=prefix, **kwargs)
self.fields["comment"].label = _("comment")

View file

@ -21,7 +21,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2.5\n" "Project-Id-Version: 2.5\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-04-22 22:06+0200\n" "POT-Creation-Date: 2020-04-23 03:10+0200\n"
"PO-Revision-Date: 2019-11-16 00:35+0100\n" "PO-Revision-Date: 2019-11-16 00:35+0100\n"
"Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n" "Last-Translator: Laouen Fernet <laouen.fernet@supelec.fr>\n"
"Language-Team: \n" "Language-Team: \n"
@ -30,44 +30,68 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: tickets/models.py:54 #: tickets/forms.py:76
msgid "comment"
msgstr "commentaire"
#: tickets/models.py:57
msgid "Title of the ticket." msgid "Title of the ticket."
msgstr "Titre du ticket." msgstr "Titre du ticket."
#: tickets/models.py:63 #: tickets/models.py:66
msgid "An email address to get back to you." msgid "An email address to get back to you."
msgstr "Une adresse mail pour vous recontacter." msgstr "Une adresse mail pour vous recontacter."
#: tickets/models.py:69 #: tickets/models.py:70
msgid "Language of the ticket."
msgstr "Langue des tickets"
#: tickets/models.py:74 tickets/models.py:170
msgid "Can view a ticket object" msgid "Can view a ticket object"
msgstr "Peut voir un objet ticket" msgstr "Peut voir un objet ticket"
#: tickets/models.py:70 #: tickets/models.py:75 tickets/models.py:171
msgid "ticket" msgid "ticket"
msgstr "ticket" msgstr "ticket"
#: tickets/models.py:71 #: tickets/models.py:76 tickets/models.py:172
msgid "tickets" msgid "tickets"
msgstr "tickets" msgstr "tickets"
#: tickets/models.py:75 #: tickets/models.py:80
#, python-format #, python-format
msgid "Ticket from %(name)s. Date: %(date)s." msgid "Ticket from %(name)s. Date: %(date)s."
msgstr "Ticket de %(name)s. Date : %(date)s." msgstr "Ticket de %(name)s. Date : %(date)s."
#: tickets/models.py:77 #: tickets/models.py:82
#, python-format #, python-format
msgid "Anonymous ticket. Date: %s." msgid "Anonymous ticket. Date: %s."
msgstr "Ticket anonyme. Date : %s." msgstr "Ticket anonyme. Date : %s."
#: tickets/models.py:111 #: tickets/models.py:90 tickets/templates/tickets/aff_ticket.html:52
msgid "Anonymous user"
msgstr "Utilisateur anonyme"
#: tickets/models.py:128
msgid "You don't have the right to view other tickets than yours." msgid "You don't have the right to view other tickets than yours."
msgstr "Vous n'avez pas le droit de voir d'autres tickets que les vôtres." msgstr "Vous n'avez pas le droit de voir d'autres tickets que les vôtres."
#: tickets/models.py:123 #: tickets/models.py:140 tickets/models.py:214
msgid "You don't have the right to view the list of tickets." msgid "You don't have the right to view the list of tickets."
msgstr "Vous n'avez pas le droit de voir la liste des tickets." msgstr "Vous n'avez pas le droit de voir la liste des tickets."
#: tickets/models.py:187
msgid "You don't have the right to view other tickets comments than yours."
msgstr "Vous n'avez pas le droit de voir d'autres tickets que les vôtres."
#: tickets/models.py:202
msgid "You don't have the right to edit other tickets comments than yours."
msgstr "Vous n'avez pas le droit d'éditer d'autres tickets que les vôtres."
#: tickets/models.py:232
msgid "Update of your ticket"
msgstr "Mise à jour de votre ticket"
#: tickets/preferences/forms.py:44 #: tickets/preferences/forms.py:44
msgid "Publish address" msgid "Publish address"
msgstr "Adresse mail de publication" msgstr "Adresse mail de publication"
@ -87,7 +111,7 @@ msgstr "Options des tickets"
msgid "Can view tickets options" msgid "Can view tickets options"
msgstr "Peut voir les options des tickets" msgstr "Peut voir les options des tickets"
#: tickets/templates/tickets/aff_ticket.html:31 #: tickets/templates/tickets/aff_ticket.html:32
#: tickets/templates/tickets/contact.html:4 #: tickets/templates/tickets/contact.html:4
#: tickets/templates/tickets/index.html:29 #: tickets/templates/tickets/index.html:29
#: tickets/templates/tickets/preferences.html:6 #: tickets/templates/tickets/preferences.html:6
@ -95,57 +119,79 @@ msgstr "Peut voir les options des tickets"
msgid "Tickets" msgid "Tickets"
msgstr "Tickets" msgstr "Tickets"
#: tickets/templates/tickets/aff_ticket.html:35 #: tickets/templates/tickets/aff_ticket.html:36
#, python-format #, python-format
msgid "Ticket #%(id)s" msgid "Ticket #%(id)s"
msgstr "Ticket #%(id)s" msgstr "Ticket #%(id)s"
#: tickets/templates/tickets/aff_ticket.html:37 #: tickets/templates/tickets/aff_ticket.html:38
#: tickets/templates/tickets/aff_tickets.html:58 #: tickets/templates/tickets/aff_tickets.html:58
msgid "Solved" msgid "Solved"
msgstr "Résolu" msgstr "Résolu"
#: tickets/templates/tickets/aff_ticket.html:39 #: tickets/templates/tickets/aff_ticket.html:40
msgid "Not solved" msgid "Not solved"
msgstr "Non résolu" msgstr "Non résolu"
#: tickets/templates/tickets/aff_ticket.html:45 #: tickets/templates/tickets/aff_ticket.html:46
msgid "Opened by" msgid "Opened by"
msgstr "Ouvert par" msgstr "Ouvert par"
#: tickets/templates/tickets/aff_ticket.html:51 #: tickets/templates/tickets/aff_ticket.html:56
msgid "Anonymous user"
msgstr "Utilisateur anonyme"
#: tickets/templates/tickets/aff_ticket.html:55
msgid "Response address: " msgid "Response address: "
msgstr "Adresse de réponse : " msgstr "Adresse de réponse : "
#: tickets/templates/tickets/aff_ticket.html:55 #: tickets/templates/tickets/aff_ticket.html:56
msgid "Response to your ticket" msgid "Response to your ticket"
msgstr "Réponse à votre ticket" msgstr "Réponse à votre ticket"
#: tickets/templates/tickets/aff_ticket.html:60
msgid "Title:"
msgstr "Titre :"
#: tickets/templates/tickets/aff_ticket.html:61 #: tickets/templates/tickets/aff_ticket.html:61
msgid "Description:" msgid "Add a comment "
msgstr "Description :" msgstr "Ajouter un commentaire"
#: tickets/templates/tickets/aff_ticket.html:65 #: tickets/templates/tickets/aff_ticket.html:64
msgid "Edit this ticket" #: tickets/templates/tickets/preferences.html:14 tickets/views.py:153
msgstr "Modifier le ticket." msgid "Edit"
msgstr "Modifier"
#: tickets/templates/tickets/aff_ticket.html:67 #: tickets/templates/tickets/aff_ticket.html:66
msgid "Mark as solved" msgid "Mark as solved"
msgstr "Marquer comme résolu" msgstr "Marquer comme résolu"
#: tickets/templates/tickets/aff_ticket.html:69 #: tickets/templates/tickets/aff_ticket.html:68
msgid "Mark as unsolved" msgid "Mark as unsolved"
msgstr "Marquer comme non résolu" msgstr "Marquer comme non résolu"
#: tickets/templates/tickets/aff_ticket.html:78 #: tickets/templates/tickets/aff_ticket.html:76
msgid "Title:"
msgstr "Titre :"
#: tickets/templates/tickets/aff_ticket.html:77
#: tickets/templates/tickets/aff_ticket.html:84
msgid "Description:"
msgstr "Description :"
#: tickets/templates/tickets/aff_ticket.html:83
msgid "Comment "
msgstr "Commentaire"
#: tickets/templates/tickets/aff_ticket.html:83
msgid " added by "
msgstr " ajouté par "
#: tickets/templates/tickets/aff_ticket.html:83
msgid " on "
msgstr " le "
#: tickets/templates/tickets/aff_ticket.html:87
msgid "Edit this comment "
msgstr "Modifier le commentaire"
#: tickets/templates/tickets/aff_ticket.html:90
msgid "Delete this comment "
msgstr "Supprimer ce commentaire"
#: tickets/templates/tickets/aff_ticket.html:99
msgid "All tickets" msgid "All tickets"
msgstr "Tous les tickets" msgstr "Tous les tickets"
@ -200,6 +246,21 @@ msgstr ""
msgid "Open a ticket" msgid "Open a ticket"
msgstr "Ouvrir un ticket" msgstr "Ouvrir un ticket"
#: tickets/templates/tickets/delete.html:29
msgid "Deletion of tickets"
msgstr "Suppression de tickets"
#: tickets/templates/tickets/delete.html:35
#, python-format
msgid ""
"Warning: are you sure you want to delete this %(objet_name)s object "
"( %(objet)s )?"
msgstr ""
#: tickets/templates/tickets/delete.html:36
msgid "Confirm"
msgstr "Confirmer"
#: tickets/templates/tickets/edit.html:34 #: tickets/templates/tickets/edit.html:34
msgid "Ticket opening" msgid "Ticket opening"
msgstr "Ouverture de ticket" msgstr "Ouverture de ticket"
@ -242,10 +303,6 @@ msgstr "Liste des tickets"
msgid "Manage the tickets" msgid "Manage the tickets"
msgstr "Gérer les tickets" msgstr "Gérer les tickets"
#: tickets/templates/tickets/preferences.html:14
msgid "Edit"
msgstr "Modifier"
#: tickets/templates/tickets/preferences.html:21 #: tickets/templates/tickets/preferences.html:21
msgid "Publication email address" msgid "Publication email address"
msgstr "Adresse mail de publication" msgstr "Adresse mail de publication"
@ -258,18 +315,38 @@ msgstr "Pas d'adresse mail, les tickets ne seront pas publiés."
msgid "No tickets" msgid "No tickets"
msgstr "Pas de tickets" msgstr "Pas de tickets"
#: tickets/views.py:56 #: tickets/views.py:62
msgid "" msgid ""
"Your ticket has been succesfully opened. We will take care of it as soon as " "Your ticket has been succesfully opened. We will take care of it as soon as "
"possible." "possible."
msgstr "" msgstr ""
"Votre ticket a bien été ouvert. Nous nous en occuperons dès que possible." "Votre ticket a bien été ouvert. Nous nous en occuperons dès que possible."
#: tickets/views.py:102 #: tickets/views.py:109
msgid "Ticket has been updated successfully" msgid "Ticket has been updated successfully"
msgstr "Le ticket a été mis à jour" msgstr "Le ticket a été mis à jour"
#: tickets/views.py:123 tickets/views.py:148 #: tickets/views.py:130
msgid "This comment was added."
msgstr "Le commentaire a été ajouté"
#: tickets/views.py:135
msgid "Add a comment"
msgstr "Ajouter un commentaire"
#: tickets/views.py:148
msgid "This comment was edited."
msgstr "Le commentaire a été édité"
#: tickets/views.py:164
msgid "The comment was deleted."
msgstr "Le commentaire a été supprimé"
#: tickets/views.py:169
msgid "Ticket Comment"
msgstr "Commentaire de ticket"
#: tickets/views.py:183 tickets/views.py:208
msgid "Never" msgid "Never"
msgstr "Jamais" msgstr "Jamais"
@ -298,9 +375,6 @@ msgstr "Jamais"
#~ "Vous n'êtes pas authentifié. Veuillez vous connecter ou fournir une " #~ "Vous n'êtes pas authentifié. Veuillez vous connecter ou fournir une "
#~ "adresse mail pour que nous puissions vous recontacter." #~ "adresse mail pour que nous puissions vous recontacter."
#~ msgid "Open the ticket"
#~ msgstr "Ouvrir le ticket"
#~ msgid "Email language" #~ msgid "Email language"
#~ msgstr "Langue du mail" #~ msgstr "Langue du mail"

View file

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.28 on 2020-04-22 21:09
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import re2o.mixins
class Migration(migrations.Migration):
dependencies = [
('tickets', '0004_auto_20200422_2127'),
]
operations = [
migrations.CreateModel(
name='CommentTicket',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField(auto_now_add=True)),
('comment', models.TextField(max_length=4095)),
],
options={
'verbose_name': 'ticket',
'verbose_name_plural': 'tickets',
'permissions': (('view_commentticket', 'Can view a ticket object'),),
},
bases=(re2o.mixins.AclMixin, models.Model),
),
migrations.AlterModelOptions(
name='ticket',
options={'permissions': (('view_ticket', 'Can view a ticket object'),), 'verbose_name': 'ticket', 'verbose_name_plural': 'tickets'},
),
migrations.AddField(
model_name='commentticket',
name='parent_ticket',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='tickets.Ticket'),
),
]

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.28 on 2020-04-23 00:02
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('tickets', '0005_auto_20200422_2309'),
]
operations = [
migrations.AddField(
model_name='commentticket',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='commentticket',
name='created_by',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ticket_comment', to=settings.AUTH_USER_MODEL),
preserve_default=False,
),
]

View file

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.28 on 2020-04-23 01:05
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('tickets', '0006_auto_20200423_0202'),
]
operations = [
migrations.AddField(
model_name='ticket',
name='language',
field=models.CharField(default='en', help_text='Language of the ticket.', max_length=16),
),
]

View file

@ -29,6 +29,9 @@ from django.utils.translation import ugettext_lazy as _
from django.template import loader from django.template import loader
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.functional import cached_property
from reversion.models import Version
from re2o.mixins import AclMixin from re2o.mixins import AclMixin
from django.core.mail import EmailMessage from django.core.mail import EmailMessage
@ -63,10 +66,12 @@ class Ticket(AclMixin, models.Model):
help_text=_("An email address to get back to you."), max_length=100, null=True help_text=_("An email address to get back to you."), max_length=100, null=True
) )
solved = models.BooleanField(default=False) solved = models.BooleanField(default=False)
request = None language = models.CharField(
max_length=16, help_text=_("Language of the ticket."), default="en"
)
class Meta: class Meta:
permissions = (("view_tickets", _("Can view a ticket object")),) permissions = (("view_ticket", _("Can view a ticket object")),)
verbose_name = _("ticket") verbose_name = _("ticket")
verbose_name_plural = _("tickets") verbose_name_plural = _("tickets")
@ -76,13 +81,25 @@ class Ticket(AclMixin, models.Model):
else: else:
return _("Anonymous ticket. Date: %s.") % (self.date) return _("Anonymous ticket. Date: %s.") % (self.date)
@cached_property
def opened_by(self):
"""Return full name of this ticket opener"""
if self.user:
return self.user.get_full_name()
else:
return _("Anonymous user")
@cached_property
def get_mail(self):
"""Return the mail of the owner of this ticket"""
return self.email or self.user.get_mail
def publish_mail(self): def publish_mail(self):
site_url = GeneralOption.get_cached_value("main_site_url") site_url = GeneralOption.get_cached_value("main_site_url")
to_addr = TicketOption.get_cached_value("publish_address") to_addr = TicketOption.get_cached_value("publish_address")
context = {"ticket": self, "site_url": site_url} context = {"ticket": self, "site_url": site_url}
language = getattr(self.request, "LANGUAGE_CODE", "en") if self.language == "fr":
if language == "fr":
obj = "Nouveau ticket ouvert" obj = "Nouveau ticket ouvert"
template = loader.get_template("tickets/publication_mail_fr") template = loader.get_template("tickets/publication_mail_fr")
else: else:
@ -94,7 +111,7 @@ class Ticket(AclMixin, models.Model):
template.render(context), template.render(context),
GeneralOption.get_cached_value("email_from"), GeneralOption.get_cached_value("email_from"),
[to_addr], [to_addr],
reply_to=[self.email], reply_to=[self.get_mail],
) )
mail_to_send.send(fail_silently=False) mail_to_send.send(fail_silently=False)
@ -103,13 +120,13 @@ class Ticket(AclMixin, models.Model):
""" Check that the user has the right to view the ticket """ Check that the user has the right to view the ticket
or that it is the author""" or that it is the author"""
if ( if (
not user_request.has_perm("tickets.view_tickets") not user_request.has_perm("tickets.view_ticket")
and self.user != user_request and self.user != user_request
): ):
return ( return (
False, False,
_("You don't have the right to view other tickets than yours."), _("You don't have the right to view other tickets than yours."),
("tickets.view_tickets",), ("tickets.view_ticket",),
) )
else: else:
return True, None, None return True, None, None
@ -117,13 +134,13 @@ class Ticket(AclMixin, models.Model):
@staticmethod @staticmethod
def can_view_all(user_request, *_args, **_kwargs): def can_view_all(user_request, *_args, **_kwargs):
""" Check that the user has access to the list of all tickets""" """ Check that the user has access to the list of all tickets"""
can = user_request.has_perm("tickets.view_tickets") can = user_request.has_perm("tickets.view_ticket")
return ( return (
can, can,
_("You don't have the right to view the list of tickets.") _("You don't have the right to view the list of tickets.")
if not can if not can
else None, else None,
("tickets.view_tickets",), ("tickets.view_ticket",),
) )
def can_create(user_request, *_args, **_kwargs): def can_create(user_request, *_args, **_kwargs):
@ -131,6 +148,97 @@ class Ticket(AclMixin, models.Model):
return True, None, None return True, None, None
class CommentTicket(AclMixin, models.Model):
"""A comment of a ticket"""
date = models.DateTimeField(auto_now_add=True)
comment = models.TextField(
max_length=4095,
blank=False,
null=False,
)
parent_ticket = models.ForeignKey(
"Ticket", on_delete=models.CASCADE
)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(
"users.User",
on_delete=models.CASCADE,
related_name="ticket_comment",
)
class Meta:
permissions = (("view_commentticket", _("Can view a ticket object")),)
verbose_name = _("ticket")
verbose_name_plural = _("tickets")
@cached_property
def comment_id(self):
return CommentTicket.objects.filter(parent_ticket=self.parent_ticket, pk__lt=self.pk).count() + 1
def can_view(self, user_request, *_args, **_kwargs):
""" Check that the user has the right to view the ticket comment
or that it is the author"""
if (
not user_request.has_perm("tickets.view_commentticket")
and self.parent_ticket.user != user_request
):
return (
False,
_("You don't have the right to view other tickets comments than yours."),
("tickets.view_commentticket",),
)
else:
return True, None, None
def can_edit(self, user_request, *_args, **_kwargs):
""" Check that the user has the right to edit the ticket comment
or that it is the author"""
if (
not user_request.has_perm("tickets.edit_commentticket")
and (self.parent_ticket.user != user_request or self.parent_ticket.user != self.created_by)
):
return (
False,
_("You don't have the right to edit other tickets comments than yours."),
("tickets.edit_commentticket",),
)
else:
return True, None, None
@staticmethod
def can_view_all(user_request, *_args, **_kwargs):
""" Check that the user has access to the list of all tickets comments"""
can = user_request.has_perm("tickets.view_commentticket")
return (
can,
_("You don't have the right to view the list of tickets.")
if not can
else None,
("tickets.view_commentticket",),
)
def __str__(self):
return "Comment " + str(self.comment_id) + " on " + str(self.parent_ticket)
def publish_mail(self):
site_url = GeneralOption.get_cached_value("main_site_url")
to_addr = TicketOption.get_cached_value("publish_address")
context = {"comment": self, "site_url": site_url}
if self.parent_ticket.language == "fr":
template = loader.get_template("tickets/update_mail_fr")
else:
template = loader.get_template("tickets/update_mail_en")
obj = _("Update of your ticket")
mail_to_send = EmailMessage(
obj,
template.render(context),
GeneralOption.get_cached_value("email_from"),
[to_addr, self.parent_ticket.get_mail],
)
mail_to_send.send(fail_silently=False)
@receiver(post_save, sender=Ticket) @receiver(post_save, sender=Ticket)
def ticket_post_save(**kwargs): def ticket_post_save(**kwargs):
""" Send the mail to publish the new ticket """ """ Send the mail to publish the new ticket """
@ -138,3 +246,12 @@ def ticket_post_save(**kwargs):
if TicketOption.get_cached_value("publish_address"): if TicketOption.get_cached_value("publish_address"):
ticket = kwargs["instance"] ticket = kwargs["instance"]
ticket.publish_mail() ticket.publish_mail()
@receiver(post_save, sender=CommentTicket)
def comment_post_save(**kwargs):
""" Send the mail to publish the new comment """
if kwargs["created"]:
if TicketOption.get_cached_value("publish_address"):
comment = kwargs["instance"]
comment.publish_mail()

View file

@ -26,6 +26,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% load bootstrap3 %} {% load bootstrap3 %}
{% load i18n %} {% load i18n %}
{% load humanize %} {% load humanize %}
{% load logs_extra %}
{% load acl %} {% load acl %}
{% block title %}{% trans "Tickets" %}{% endblock %} {% block title %}{% trans "Tickets" %}{% endblock %}
@ -54,28 +55,48 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% if not ticket.user %} {% if not ticket.user %}
{% trans "Response address: " %}<A HREF="mailto:{{ticket.email}}?subject={% trans "Response to your ticket"%}">{{ticket.email}}</A> {% trans "Response address: " %}<A HREF="mailto:{{ticket.email}}?subject={% trans "Response to your ticket"%}">{{ticket.email}}</A>
{% endif %} {% endif %}
<div class="text-right">
{% can_view ticket %}
<a class="btn btn-info btn-sm" role="button" href="{% url 'tickets:add-comment' ticket.id %}"><i class="fa fa-plus"></i> {% trans "Add a comment " %}</a>
{% acl_end %}
{% can_edit ticket %}
<a class="btn btn-info btn-sm" role="button" href="{% url 'tickets:edit-ticket' ticket.id %}"><i class="fa fa-edit"></i> {% trans "Edit" %}</a>
{% if not ticket.solved %}
<a class="btn btn-success btn-sm" role="button" href="{% url 'tickets:change-ticket-status' ticket.id %}"><i class="fa fa-check"></i> {% trans "Mark as solved" %}</a>
{% else %}
<a class="btn btn-warning btn-sm" role="button" href="{% url 'tickets:change-ticket-status' ticket.id %}"><i class="fa fa-close"></i> {% trans "Mark as unsolved" %}</a>
{% endif %}
{% acl_end %}
{% history_button ticket text=True %}
</div>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<p><b>{% trans "Title:" %}</b> {{ticket.title}}</p> <p><b>{% trans "Title:" %}</b> {{ticket.title}}</p>
<p><b>{% trans "Description:" %}</b> {{ ticket.description | linebreaks }}</p> <b>{% trans "Description:" %}</b> {{ ticket.description | linebreaks }}
<div class="text-right"> </div>
{% can_edit ticket %}
<a type="button" href="{% url 'tickets:edit-ticket' ticket.id %}" class="btn btn-info"><p>{% trans "Edit this ticket" %}</p></a> {% for comment in comments %}
{% if not ticket.solved %} <div class="panel-footer">
<a type="button" href="{% url 'tickets:change-ticket-status' ticket.id %}" class="btn btn-success"><p>{% trans "Mark as solved" %}</p></a> <p><span class="badge">{% trans "Comment " %}<b>#{{comment.comment_id}}</b></span> {% trans " added by " %}{{ comment.created_by.get_full_name }}{% trans " on " %} {{comment.created_at}}</p>
{% else %} <b>{% trans "Description:" %}</b> {{ comment.comment | linebreaks }}
<a type="button" href="{% url 'tickets:change-ticket-status' ticket.id %}" class="btn btn-warning"><p>{% trans "Mark as unsolved" %}</p></a> <div class="text-right">
{% endif %} {% can_edit comment %}
<a class="btn btn-info btn-sm" role="button" href="{% url 'tickets:edit-comment' comment.id %}"><i class="fa fa-edit"></i> {% trans "Edit this comment " %}</a>
{% acl_end %} {% acl_end %}
{% can_delete comment %}
<a class="btn btn-danger btn-sm" role="button" href="{% url 'tickets:del-comment' comment.id %}"><i class="fa fa-close"></i> {% trans "Delete this comment " %}</a>
{% acl_end %}
{% history_button comment text=True %}
</div>
</div> </div>
{% endfor %}
</div> </div>
</div>
<div class="text-right"> <div class="text-right">
<a type="button" href="{% url 'tickets:aff-tickets' %}" class="btn btn-primary"><p>{% trans "All tickets" %}</p></a> <a class="btn btn-primary" role="button" href="{% url 'tickets:aff-tickets' %}"><i class="fa fa-reorder"></i> {% trans "All tickets" %}</a>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,43 @@
{% extends 'users/sidebar.html' %}
{% comment %}
Re2o est un logiciel d'administration développé initiallement au rezometz. Il
se veut agnostique au réseau considéré, de manière à être installable en
quelques clics.
Copyright © 2017 Gabriel Détraz
Copyright © 2017 Lara Kermarec
Copyright © 2017 Augustin Lemesle
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
{% endcomment %}
{% load bootstrap3 %}
{% load i18n %}
{% block title %}{% trans "Deletion of tickets" %}{% endblock %}
{% block content %}
<form class="form" method="post">
{% csrf_token %}
<h4>{% blocktrans %}Warning: are you sure you want to delete this {{ objet_name }} object ( {{ objet }} )?{% endblocktrans %}</h4>
{% trans "Confirm" as tr_confirm %}
{% bootstrap_button tr_confirm button_type="submit" icon="trash" button_class='btn-danger' %}
</form>
<br />
<br />
<br />
{% endblock %}

View file

@ -1,12 +1,12 @@
{% if ticket.user %} {{ ticket.user.get_full_name }} opened a ticket. {% if ticket.user %} {{ ticket.user.get_full_name }} opened a ticket.
Profile: {{site_url}}{% url 'users:profil' ticket.user.id%} Profile: {{site_url}}{% url 'users:profil' ticket.user.id%}
Answer to the address: {{ticket.user.get_mail}}.
{% else %} {% else %}
An anonymous user (not authenticated) opened a ticket An anonymous user (not authenticated) opened a ticket
Answer to the address:{{ticket.email}}.
{% endif %} {% endif %}
Answer to the address: {{{ticket.get_mail}}.
Title: {{ ticket.title | safe }} Title: {{ ticket.title | safe }}
Description: {{ ticket.description | safe }} Description: {{ ticket.description | safe }}

View file

@ -1,11 +1,11 @@
{% if ticket.user %} {{ ticket.user.get_full_name }} a ouvert un ticket. {% if ticket.user %} {{ ticket.user.get_full_name }} a ouvert un ticket.
Profil : {{site_url}}{% url 'users:profil' ticket.user.id%} Profil : {{site_url}}{% url 'users:profil' ticket.user.id%}
Répondre à l'adresse : {{ticket.user.get_mail}}.
{% else %} {% else %}
Un utilisateur anonyme (non connecté) a ouvert un ticket. Un utilisateur anonyme (non connecté) a ouvert un ticket.
Répondre à l'adresse : {{ticket.email}}.
{% endif %} {% endif %}
Répondre à l'adresse : {{ticket.get_mail}}.
Titre : {{ ticket.title | safe }} Titre : {{ ticket.title | safe }}

View file

@ -0,0 +1,12 @@
Hello,
The ticket {{ comment.parent_ticket.title | safe }} n°{{ comment.parent_ticket.id }}, opened by {{ comment.parent_ticket.opened_by }}, has been updated by {{ comment.created_by.get_full_name | safe }}.
{% if comment.parent_ticket.user %}
The complete re2o profil can be found here : {{site_url}}{% url 'users:profil' comment.parent_ticket.user.id%}
{% endif %}
Description : {{ comment.comment | safe }}
Best regards,
The member of the association

View file

@ -0,0 +1,12 @@
Bonjour,
Le ticket {{ comment.parent_ticket.title | safe }} n°{{ comment.parent_ticket.id }}, ouvert par {{ comment.parent_ticket.opened_by }}, a reçu une mise à jour par {{ comment.created_by.get_full_name | safe }}.
{% if comment.parent_ticket.user %}
Le profil re2o est accessible à l'adresse suivante : {{site_url}}{% url 'users:profil' comment.parent_ticket.user.id%}
{% endif %}
Description : {{ comment.comment | safe }}
Cordialement,
Les membres actifs de l'association

View file

@ -39,4 +39,7 @@ urlpatterns = [
name="edit-options", name="edit-options",
), ),
url(r"^new_ticket/$", views.new_ticket, name="new-ticket"), url(r"^new_ticket/$", views.new_ticket, name="new-ticket"),
url(r"^add_comment/(?P<ticketid>[0-9]+)$", views.add_comment, name="add-comment"),
url(r"^edit_comment/(?P<commentticketid>[0-9]+)$", views.edit_comment, name="edit-comment"),
url(r"^del_comment/(?P<commentticketid>[0-9]+)$", views.del_comment, name="del-comment"),
] ]

View file

@ -36,13 +36,19 @@ from re2o.views import form
from re2o.base import re2o_paginator from re2o.base import re2o_paginator
from re2o.acl import can_view, can_view_all, can_edit, can_create from re2o.acl import (
can_view,
can_view_all,
can_edit,
can_create,
can_delete
)
from preferences.models import GeneralOption from preferences.models import GeneralOption
from .models import Ticket from .models import Ticket, CommentTicket
from .forms import NewTicketForm, EditTicketForm from .forms import NewTicketForm, EditTicketForm, CommentTicketForm
def new_ticket(request): def new_ticket(request):
@ -71,10 +77,11 @@ def new_ticket(request):
@can_view(Ticket) @can_view(Ticket)
def aff_ticket(request, ticket, ticketid): def aff_ticket(request, ticket, ticketid):
"""View to display only one ticket""" """View to display only one ticket"""
comments = CommentTicket.objects.filter(parent_ticket=ticket)
return render( return render(
request, request,
"tickets/aff_ticket.html", "tickets/aff_ticket.html",
{"ticket": ticket}, {"ticket": ticket, "comments": comments},
) )
@ -110,6 +117,59 @@ def edit_ticket(request, ticket, ticketid):
) )
@login_required
@can_view(Ticket)
def add_comment(request, ticket, ticketid):
""" Add a comment to a ticket"""
commentticket = CommentTicketForm(request.POST or None)
if commentticket.is_valid():
commentticket = commentticket.save(commit=False)
commentticket.parent_ticket = ticket
commentticket.created_by = request.user
commentticket.save()
messages.success(request, _("This comment was added."))
return redirect(
reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)})
)
return form(
{"ticketform": commentticket, "action_name": _("Add a comment")}, "tickets/edit.html", request
)
@login_required
@can_edit(CommentTicket)
def edit_comment(request, commentticket_instance, **_kwargs):
""" Edit a comment of a ticket"""
commentticket = CommentTicketForm(request.POST or None, instance=commentticket_instance)
if commentticket.is_valid():
ticketid = commentticket_instance.parent_ticket.id
if commentticket.changed_data:
commentticket.save()
messages.success(request, _("This comment was edited."))
return redirect(
reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)})
)
return form(
{"ticketform": commentticket, "action_name": _("Edit")}, "tickets/edit.html", request,
)
@login_required
@can_delete(CommentTicket)
def del_comment(request, commentticket, **_kwargs):
"""Delete a comment of a ticket"""
if request.method == "POST":
ticketid = commentticket.parent_ticket.id
commentticket.delete()
messages.success(request, _("The comment was deleted."))
return redirect(
reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)})
)
return form(
{"objet": commentticket, "objet_name": _("Ticket Comment")}, "tickets/delete.html", request
)
@login_required @login_required
@can_view_all(Ticket) @can_view_all(Ticket)
def aff_tickets(request): def aff_tickets(request):