diff --git a/tickets/api/__init__.py b/tickets/api/__init__.py new file mode 100644 index 00000000..95754e37 --- /dev/null +++ b/tickets/api/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2021 nanoy +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + diff --git a/tickets/api/serializers.py b/tickets/api/serializers.py new file mode 100644 index 00000000..56f94d4a --- /dev/null +++ b/tickets/api/serializers.py @@ -0,0 +1,55 @@ +# -*- mode: python; coding: utf-8 -*- +# Re2o est un logiciel d'administration développé initiallement au Rézo Metz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2018 Maël Kervella +# +# 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. + +from rest_framework import serializers + +from tickets.models import Ticket, CommentTicket +from api.serializers import NamespacedHMSerializer + + +class TicketSerializer(NamespacedHMSerializer): + """Serialize `tickets.models.Ticket` objects.""" + + class Meta: + model = Ticket + fields = ("id", "title", "description", "email", "uuid") + + +class CommentTicketSerializer(NamespacedHMSerializer): + uuid = serializers.UUIDField() + + class Meta: + model = CommentTicket + fields = ("comment", "uuid", "parent_ticket", "created_at", "created_by") + read_only_fields = ("parent_ticket", "created_at", "created_by") + extra_kwargs = { + "uuid": {"write_only": True}, + } + + def create(self, validated_data): + validated_data = { + "comment": validated_data["comment"], + "parent_ticket": Ticket.objects.get(uuid=validated_data["uuid"]), + "created_by": validated_data["created_by"], + } + comment = CommentTicket(**validated_data) + comment.save() + return comment diff --git a/tickets/api/urls.py b/tickets/api/urls.py new file mode 100644 index 00000000..f991799b --- /dev/null +++ b/tickets/api/urls.py @@ -0,0 +1,31 @@ +# -*- mode: python; coding: utf-8 -*- +# Re2o est un logiciel d'administration développé initiallement au Rézo Metz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2018 Maël Kervella +# +# 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. + +from . import views + +urls_viewset = [ + (r"tickets/tickets", views.TicketsViewSet, None), + (r"tickets/comments", views.CommentTicketViewSet, None), +] + +# urls_view = [ +# (r"ticket/tickets", ), +# ] diff --git a/tickets/api/views.py b/tickets/api/views.py new file mode 100644 index 00000000..32e17ae7 --- /dev/null +++ b/tickets/api/views.py @@ -0,0 +1,51 @@ +# -*- mode: python; coding: utf-8 -*- +# Re2o est un logiciel d'administration développé initiallement au Rézo Metz. Il +# se veut agnostique au réseau considéré, de manière à être installable en +# quelques clics. +# +# Copyright © 2018 Maël Kervella +# +# 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. + +from rest_framework import viewsets + +from tickets.models import Ticket, CommentTicket +from api.permissions import ACLPermission + +from . import serializers + + +class TicketsViewSet(viewsets.ModelViewSet): + + permission_classes = (ACLPermission,) + perms_map = { + "GET": [Ticket.can_view_all], + "POST": [], + } + serializer_class = serializers.TicketSerializer + queryset = Ticket.objects.all() + + +class CommentTicketViewSet(viewsets.ModelViewSet): + permission_classes = (ACLPermission,) + perms_map = { + "GET": [Ticket.can_view_all], + "POST": [], + } + serializer_class = serializers.CommentTicketSerializer + queryset = CommentTicket.objects.all() + + def perform_create(self, serializer): + serializer.save(created_by=self.request.user) diff --git a/tickets/migrations/0003_ticket_uuid.py b/tickets/migrations/0003_ticket_uuid.py new file mode 100644 index 00000000..854190b0 --- /dev/null +++ b/tickets/migrations/0003_ticket_uuid.py @@ -0,0 +1,19 @@ +# Generated by Django 2.2.18 on 2021-05-09 17:29 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('tickets', '0002_auto_20210214_1046'), + ] + + operations = [ + migrations.AddField( + model_name='ticket', + name='uuid', + field=models.UUIDField(default=uuid.uuid4, editable=False), + ), + ] diff --git a/tickets/models.py b/tickets/models.py index 3bae040a..d8785292 100644 --- a/tickets/models.py +++ b/tickets/models.py @@ -25,6 +25,9 @@ Ticket model from __future__ import absolute_import +import uuid + +from django.core.mail import EmailMessage from django.db import models from django.utils.translation import ugettext_lazy as _ from django.template import loader @@ -85,8 +88,9 @@ class Ticket(AclMixin, models.Model): ) solved = models.BooleanField(default=False) language = models.CharField( - max_length=16, help_text=_("Language of the ticket."), default="en" + max_length=16, help_text=_("Language of the ticket."), default="en" ) + uuid = models.UUIDField(default=uuid.uuid4, editable=False) request = None class Meta: @@ -96,7 +100,9 @@ class Ticket(AclMixin, models.Model): def __str__(self): if self.user: - return _("Ticket from {name}. Date: {date}.").format(name=self.user.get_full_name(),date=self.date) + return _("Ticket from {name}. Date: {date}.").format( + name=self.user.get_full_name(), date=self.date + ) else: return _("Anonymous ticket. Date: %s.") % (self.date) @@ -106,12 +112,12 @@ class Ticket(AclMixin, models.Model): if self.user: return self.user.get_full_name() else: - return _("Anonymous user") + return _("Anonymous user") @cached_property def get_mail(self): """Get the email address of the user who opened the ticket.""" - return self.email or self.user.get_mail + return self.email or self.user.get_mail def publish_mail(self): """Send an email for a newly opened ticket to the address set in the @@ -134,10 +140,10 @@ class Ticket(AclMixin, models.Model): GeneralOption.get_cached_value("email_from"), [to_addr], reply_to=[self.get_mail], + headers={"re2o-uuid": self.uuid}, ) send_mail_object(mail_to_send, self.request) - def can_view(self, user_request, *_args, **_kwargs): """Check that the user has the right to view the ticket or that it is the author.""" @@ -189,9 +195,7 @@ class CommentTicket(AclMixin, models.Model): blank=False, null=False, ) - parent_ticket = models.ForeignKey( - "Ticket", on_delete=models.CASCADE - ) + parent_ticket = models.ForeignKey("Ticket", on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) created_by = models.ForeignKey( "users.User", @@ -207,7 +211,16 @@ class CommentTicket(AclMixin, models.Model): @cached_property def comment_id(self): - return CommentTicket.objects.filter(parent_ticket=self.parent_ticket, pk__lt=self.pk).count() + 1 + return ( + CommentTicket.objects.filter( + parent_ticket=self.parent_ticket, pk__lt=self.pk + ).count() + + 1 + ) + + @property + def uuid(self): + return self.parent_ticket.uuid def can_view(self, user_request, *_args, **_kwargs): """Check that the user has the right to view the ticket comment @@ -218,7 +231,9 @@ class CommentTicket(AclMixin, models.Model): ): return ( False, - _("You don't have the right to view other tickets comments than yours."), + _( + "You don't have the right to view other tickets comments than yours." + ), ("tickets.view_commentticket",), ) else: @@ -227,13 +242,15 @@ class CommentTicket(AclMixin, models.Model): 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.change_commentticket") - and (self.parent_ticket.user != user_request or self.parent_ticket.user != self.created_by) + if not user_request.has_perm("tickets.change_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."), + _( + "You don't have the right to edit other tickets comments than yours." + ), ("tickets.change_commentticket",), ) else: @@ -273,6 +290,7 @@ class CommentTicket(AclMixin, models.Model): GeneralOption.get_cached_value("email_from"), [to_addr, self.parent_ticket.get_mail], reply_to=[to_addr, self.parent_ticket.get_mail], + headers={"re2o-uuid": self.uuid}, ) send_mail_object(mail_to_send, self.request) diff --git a/tickets/templates/tickets/aff_ticket.html b/tickets/templates/tickets/aff_ticket.html index c5ad6cda..4f54499e 100644 --- a/tickets/templates/tickets/aff_ticket.html +++ b/tickets/templates/tickets/aff_ticket.html @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., {% else %} {% trans "Not solved" %} {% endif %} +{{ ticket.uuid }}