8
0
Fork 0
mirror of https://gitlab2.federez.net/re2o/re2o synced 2024-11-24 20:33:11 +00:00

feat: add first part of ticket api

This commit is contained in:
Yoann Pétri 2021-05-10 20:56:34 +02:00
parent 0a1dc9edd8
commit f4f6a70de2
Signed by: nanoy
GPG key ID: DC24C5787C943389
9 changed files with 258 additions and 42 deletions

14
tickets/api/__init__.py Normal file
View file

@ -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.

View file

@ -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

31
tickets/api/urls.py Normal file
View file

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

51
tickets/api/views.py Normal file
View file

@ -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)

View file

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

View file

@ -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)

View file

@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
{% else %}
<span class="badge badge-danger">{% trans "Not solved" %}</span>
{% endif %}
<span class="badge badge-info">{{ ticket.uuid }}</span>
</h2>
<div class="panel panel-default">

View file

@ -29,17 +29,30 @@ from . import views
from .preferences.views import edit_options
urlpatterns = [
url(r"^$", views.aff_tickets, name="aff-tickets"),
url(r"^(?P<ticketid>[0-9]+)$", views.aff_ticket, name="aff-ticket"),
url(r"^change_ticket_status/(?P<ticketid>[0-9]+)$", views.change_ticket_status, name="change-ticket-status"),
url(r"^edit_ticket/(?P<ticketid>[0-9]+)$", views.edit_ticket, name="edit-ticket"),
url(
path("", views.aff_tickets, name="aff-tickets"),
path("<int:ticketid>", views.aff_ticket, name="aff-ticket"),
path("by_uuid/<uuid:ticketuuid>", views.aff_ticket_uuid, name="aff-ticket-uuid"),
path(
"change_ticket_status/<int:ticketid>",
views.change_ticket_status,
name="change-ticket-status",
),
path("edit_ticket/<int:ticketid>", views.edit_ticket, name="edit-ticket"),
re_path(
r"^edit_options/(?P<section>TicketOption)$",
edit_options,
name="edit-options",
),
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"),
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

@ -22,7 +22,8 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
from django.forms import modelformset_factory
from django.shortcuts import get_object_or_404, redirect, render
from django.template.loader import render_to_string
from django.views.decorators.cache import cache_page
from django.utils.translation import ugettext as _
@ -32,13 +33,7 @@ from re2o.views import form
from re2o.base import re2o_paginator
from re2o.acl import (
can_view,
can_view_all,
can_edit,
can_create,
can_delete
)
from re2o.acl import can_view, can_view_all, can_edit, can_create, can_delete
from preferences.models import GeneralOption
@ -62,10 +57,12 @@ def new_ticket(request):
return redirect(reverse("index"))
else:
return redirect(
reverse("users:profil", kwargs={"userid": str(request.user.id)})
reverse("users:profil", kwargs={"userid": str(request.user.id)})
)
return form(
{"ticketform": ticketform, 'action_name': ("Create a ticket")}, "tickets/edit.html", request
{"ticketform": ticketform, "action_name": ("Create a ticket")},
"tickets/edit.html",
request,
)
@ -81,15 +78,24 @@ def aff_ticket(request, ticket, ticketid):
)
def aff_ticket_uuid(request, ticketuuid):
"""View used to display a single ticket."""
ticket = get_object_or_404(Ticket, uuid=ticketuuid)
comments = CommentTicket.objects.filter(parent_ticket=ticket)
return render(
request,
"tickets/aff_ticket.html",
{"ticket": ticket, "comments": comments},
)
@login_required
@can_edit(Ticket)
def change_ticket_status(request, ticket, ticketid):
"""View used to change a ticket's status."""
ticket.solved = not ticket.solved
ticket.save()
return redirect(
reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)})
)
return redirect(reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)}))
@login_required
@ -101,15 +107,15 @@ def edit_ticket(request, ticket, ticketid):
ticketform.save()
messages.success(
request,
_(
"Ticket has been updated successfully"
),
_("Ticket has been updated successfully"),
)
return redirect(
reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)})
)
return form(
{"ticketform": ticketform, 'action_name': ("Edit this ticket")}, "tickets/edit.html", request
{"ticketform": ticketform, "action_name": ("Edit this ticket")},
"tickets/edit.html",
request,
)
@ -128,7 +134,9 @@ def add_comment(request, ticket, ticketid):
reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)})
)
return form(
{"ticketform": commentticket, "action_name": _("Add a comment")}, "tickets/edit.html", request
{"ticketform": commentticket, "action_name": _("Add a comment")},
"tickets/edit.html",
request,
)
@ -136,7 +144,9 @@ def add_comment(request, ticket, ticketid):
@can_edit(CommentTicket)
def edit_comment(request, commentticket_instance, **_kwargs):
"""View used to edit a comment of a ticket."""
commentticket = CommentTicketForm(request.POST or None, instance=commentticket_instance)
commentticket = CommentTicketForm(
request.POST or None, instance=commentticket_instance
)
if commentticket.is_valid():
ticketid = commentticket_instance.parent_ticket.id
if commentticket.changed_data:
@ -146,7 +156,9 @@ def edit_comment(request, commentticket_instance, **_kwargs):
reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)})
)
return form(
{"ticketform": commentticket, "action_name": _("Edit")}, "tickets/edit.html", request,
{"ticketform": commentticket, "action_name": _("Edit")},
"tickets/edit.html",
request,
)
@ -162,7 +174,9 @@ def del_comment(request, commentticket, **_kwargs):
reverse("tickets:aff-ticket", kwargs={"ticketid": str(ticketid)})
)
return form(
{"objet": commentticket, "objet_name": _("Ticket Comment")}, "tickets/delete.html", request
{"objet": commentticket, "objet_name": _("Ticket Comment")},
"tickets/delete.html",
request,
)