mirror of
https://gitea.mueller.network/extern/django-helpdesk.git
synced 2024-11-25 09:23:39 +01:00
Implement My Tickets view in public helpdesk
Note: This is a breaking change as it forces pagination on the API endoints. This should have been done from the start as the API without pagination is useless when there are large numbers of tickets.
This commit is contained in:
parent
cec90aafdd
commit
b92c83de39
@ -70,6 +70,46 @@ class DatatablesTicketSerializer(serializers.ModelSerializer):
|
|||||||
return obj.kbitem.title if obj.kbitem else ""
|
return obj.kbitem.title if obj.kbitem else ""
|
||||||
|
|
||||||
|
|
||||||
|
class PublicTicketListingSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
A serializer to be used by the public API for listing tickets. Don't expose private fields here!
|
||||||
|
"""
|
||||||
|
ticket = serializers.SerializerMethodField()
|
||||||
|
submitter = serializers.SerializerMethodField()
|
||||||
|
created = serializers.SerializerMethodField()
|
||||||
|
due_date = serializers.SerializerMethodField()
|
||||||
|
status = serializers.SerializerMethodField()
|
||||||
|
queue = serializers.SerializerMethodField()
|
||||||
|
kbitem = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Ticket
|
||||||
|
# fields = '__all__'
|
||||||
|
fields = ('ticket', 'id', 'title', 'queue', 'status',
|
||||||
|
'created', 'due_date', 'submitter', 'kbitem')
|
||||||
|
|
||||||
|
def get_queue(self, obj):
|
||||||
|
return {"title": obj.queue.title, "id": obj.queue.id}
|
||||||
|
|
||||||
|
def get_ticket(self, obj):
|
||||||
|
return str(obj.id) + " " + obj.ticket
|
||||||
|
|
||||||
|
def get_status(self, obj):
|
||||||
|
return obj.get_status
|
||||||
|
|
||||||
|
def get_created(self, obj):
|
||||||
|
return humanize.naturaltime(obj.created)
|
||||||
|
|
||||||
|
def get_due_date(self, obj):
|
||||||
|
return humanize.naturaltime(obj.due_date)
|
||||||
|
|
||||||
|
def get_submitter(self, obj):
|
||||||
|
return obj.submitter_email
|
||||||
|
|
||||||
|
def get_kbitem(self, obj):
|
||||||
|
return obj.kbitem.title if obj.kbitem else ""
|
||||||
|
|
||||||
|
|
||||||
class FollowUpAttachmentSerializer(serializers.ModelSerializer):
|
class FollowUpAttachmentSerializer(serializers.ModelSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = FollowUpAttachment
|
model = FollowUpAttachment
|
||||||
|
69
helpdesk/templates/helpdesk/my_tickets.html
Normal file
69
helpdesk/templates/helpdesk/my_tickets.html
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{% extends "helpdesk/public_base.html" %}{% load i18n %}
|
||||||
|
|
||||||
|
{% block helpdesk_body %}
|
||||||
|
<h2>{% trans "My Tickets" %}</h2>
|
||||||
|
|
||||||
|
<div class="container mt-4">
|
||||||
|
<table class="table table-striped" id="ticketsTable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Queue</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Created</th>
|
||||||
|
<th>Due Date</th>
|
||||||
|
<th>Submitter</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<!-- Rows will be added here dynamically using jQuery -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<nav aria-label="Page navigation">
|
||||||
|
<ul class="pagination" id="pagination">
|
||||||
|
<!-- Pagination buttons will be added here dynamically -->
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// don't use jquery's document ready but rather the more basic window load
|
||||||
|
// because we need to wait for the page to load before we can fetch the tickets
|
||||||
|
window.addEventListener('load', function()
|
||||||
|
{
|
||||||
|
function fetchTickets(page = 1) {
|
||||||
|
const endpoint = '{% url "helpdesk:user_tickets-list" %}?page=' + page;
|
||||||
|
|
||||||
|
$.get(endpoint, function(data) {
|
||||||
|
$('#ticketsTable tbody').empty();
|
||||||
|
data.results.forEach(function(ticket) {
|
||||||
|
$('#ticketsTable tbody').append(`
|
||||||
|
<tr>
|
||||||
|
<td><a href="/view/?ticket=${ticket.id}&email=${ticket.submitter}">${ticket.title}</a></td>
|
||||||
|
<td>${ticket.queue.title}</td>
|
||||||
|
<td>${ticket.status}</td>
|
||||||
|
<td>${ticket.created}</td>
|
||||||
|
<td>${ticket.due_date ? ticket.due_date : 'N/A'}</td>
|
||||||
|
<td>${ticket.submitter}</td>
|
||||||
|
</tr>
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#pagination').empty();
|
||||||
|
for (let i = 1; i <= data.total_pages; i++) {
|
||||||
|
$('#pagination').append(`
|
||||||
|
<li class="page-item ${i === data.page ? 'active' : ''}">
|
||||||
|
<a class="page-link" href="#" data-page="${i}">${i}</a>
|
||||||
|
</li>
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchTickets();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -68,6 +68,15 @@
|
|||||||
<span>{% trans "New Ticket" %}</span>
|
<span>{% trans "New Ticket" %}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
{% if user.is_authenticated %}
|
||||||
|
<li class="nav-item{% if 'my-tickets' in request.path %} active{% endif %}">
|
||||||
|
<a class="nav-link" href="{% url 'helpdesk:my-tickets' %}">
|
||||||
|
<i class="fas fa-fw fa-tasks"></i>
|
||||||
|
<span>{% trans "My Tickets" %}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
{% if helpdesk_settings.HELPDESK_KB_ENABLED %}
|
{% if helpdesk_settings.HELPDESK_KB_ENABLED %}
|
||||||
<li class="nav-item{% if 'kb' in request.path %} active{% endif %}">
|
<li class="nav-item{% if 'kb' in request.path %} active{% endif %}">
|
||||||
<a class="nav-link" href="{% url 'helpdesk:kb_index' %}">
|
<a class="nav-link" href="{% url 'helpdesk:kb_index' %}">
|
||||||
|
@ -14,7 +14,7 @@ from django.views.generic import TemplateView
|
|||||||
from helpdesk import settings as helpdesk_settings
|
from helpdesk import settings as helpdesk_settings
|
||||||
from helpdesk.decorators import helpdesk_staff_member_required, protect_view
|
from helpdesk.decorators import helpdesk_staff_member_required, protect_view
|
||||||
from helpdesk.views import feeds, login, public, staff
|
from helpdesk.views import feeds, login, public, staff
|
||||||
from helpdesk.views.api import CreateUserView, FollowUpAttachmentViewSet, FollowUpViewSet, TicketViewSet
|
from helpdesk.views.api import CreateUserView, FollowUpAttachmentViewSet, FollowUpViewSet, TicketViewSet, UserTicketViewSet
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
|
|
||||||
@ -154,6 +154,7 @@ if helpdesk_settings.HELPDESK_ENABLE_DEPENDENCIES_ON_TICKET:
|
|||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
path("", protect_view(public.Homepage.as_view()), name="home"),
|
path("", protect_view(public.Homepage.as_view()), name="home"),
|
||||||
|
path("tickets/my-tickets/", public.MyTickets.as_view(), name="my-tickets"),
|
||||||
path("tickets/submit/", public.create_ticket, name="submit"),
|
path("tickets/submit/", public.create_ticket, name="submit"),
|
||||||
path(
|
path(
|
||||||
"tickets/submit_iframe/",
|
"tickets/submit_iframe/",
|
||||||
@ -199,10 +200,9 @@ urlpatterns += [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
# API is added to url conf based on the setting (False by default)
|
|
||||||
if helpdesk_settings.HELPDESK_ACTIVATE_API_ENDPOINT:
|
|
||||||
router = DefaultRouter()
|
router = DefaultRouter()
|
||||||
router.register(r"tickets", TicketViewSet, basename="ticket")
|
router.register(r"tickets", TicketViewSet, basename="ticket")
|
||||||
|
router.register(r"user_tickets", UserTicketViewSet, basename="user_tickets")
|
||||||
router.register(r"followups", FollowUpViewSet, basename="followups")
|
router.register(r"followups", FollowUpViewSet, basename="followups")
|
||||||
router.register(r"followups-attachments",
|
router.register(r"followups-attachments",
|
||||||
FollowUpAttachmentViewSet, basename="followupattachments")
|
FollowUpAttachmentViewSet, basename="followupattachments")
|
||||||
|
@ -1,10 +1,29 @@
|
|||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from helpdesk.models import FollowUp, FollowUpAttachment, Ticket
|
from helpdesk.models import FollowUp, FollowUpAttachment, Ticket
|
||||||
from helpdesk.serializers import FollowUpAttachmentSerializer, FollowUpSerializer, TicketSerializer, UserSerializer
|
from helpdesk.serializers import FollowUpAttachmentSerializer, FollowUpSerializer, TicketSerializer, UserSerializer, PublicTicketListingSerializer
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
from rest_framework.mixins import CreateModelMixin
|
from rest_framework.mixins import CreateModelMixin
|
||||||
from rest_framework.permissions import IsAdminUser
|
from rest_framework.permissions import IsAdminUser
|
||||||
from rest_framework.viewsets import GenericViewSet
|
from rest_framework.viewsets import GenericViewSet
|
||||||
|
from rest_framework.pagination import PageNumberPagination
|
||||||
|
|
||||||
|
|
||||||
|
class ConservativePagination(PageNumberPagination):
|
||||||
|
page_size = 25
|
||||||
|
page_size_query_param = 'page_size'
|
||||||
|
|
||||||
|
|
||||||
|
class UserTicketViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
"""
|
||||||
|
A list of all the tickets submitted by the current user
|
||||||
|
|
||||||
|
The view is paginated by default
|
||||||
|
"""
|
||||||
|
serializer_class = PublicTicketListingSerializer
|
||||||
|
pagination_class = ConservativePagination
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return Ticket.objects.filter(submitter_email=self.request.user.email).order_by('-created')
|
||||||
|
|
||||||
|
|
||||||
class TicketViewSet(viewsets.ModelViewSet):
|
class TicketViewSet(viewsets.ModelViewSet):
|
||||||
@ -13,6 +32,7 @@ class TicketViewSet(viewsets.ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
queryset = Ticket.objects.all()
|
queryset = Ticket.objects.all()
|
||||||
serializer_class = TicketSerializer
|
serializer_class = TicketSerializer
|
||||||
|
pagination_class = ConservativePagination
|
||||||
permission_classes = [IsAdminUser]
|
permission_classes = [IsAdminUser]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
@ -30,12 +50,14 @@ class TicketViewSet(viewsets.ModelViewSet):
|
|||||||
class FollowUpViewSet(viewsets.ModelViewSet):
|
class FollowUpViewSet(viewsets.ModelViewSet):
|
||||||
queryset = FollowUp.objects.all()
|
queryset = FollowUp.objects.all()
|
||||||
serializer_class = FollowUpSerializer
|
serializer_class = FollowUpSerializer
|
||||||
|
pagination_class = ConservativePagination
|
||||||
permission_classes = [IsAdminUser]
|
permission_classes = [IsAdminUser]
|
||||||
|
|
||||||
|
|
||||||
class FollowUpAttachmentViewSet(viewsets.ModelViewSet):
|
class FollowUpAttachmentViewSet(viewsets.ModelViewSet):
|
||||||
queryset = FollowUpAttachment.objects.all()
|
queryset = FollowUpAttachment.objects.all()
|
||||||
serializer_class = FollowUpAttachmentSerializer
|
serializer_class = FollowUpAttachmentSerializer
|
||||||
|
pagination_class = ConservativePagination
|
||||||
permission_classes = [IsAdminUser]
|
permission_classes = [IsAdminUser]
|
||||||
|
|
||||||
|
|
||||||
|
@ -208,12 +208,14 @@ class ViewTicket(TemplateView):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
queue, ticket_id = Ticket.queue_and_id_from_query(ticket_req)
|
queue, ticket_id = Ticket.queue_and_id_from_query(ticket_req)
|
||||||
|
if request.user.is_authenticated and request.user.email == email:
|
||||||
|
ticket = Ticket.objects.get(id=ticket_id, submitter_email__iexact=email)
|
||||||
if hasattr(settings, 'HELPDESK_VIEW_A_TICKET_PUBLIC') and settings.HELPDESK_VIEW_A_TICKET_PUBLIC:
|
if hasattr(settings, 'HELPDESK_VIEW_A_TICKET_PUBLIC') and settings.HELPDESK_VIEW_A_TICKET_PUBLIC:
|
||||||
ticket = Ticket.objects.get(id=ticket_id, submitter_email__iexact=email)
|
ticket = Ticket.objects.get(id=ticket_id, submitter_email__iexact=email)
|
||||||
else:
|
else:
|
||||||
ticket = Ticket.objects.get(id=ticket_id, submitter_email__iexact=email, secret_key__iexact=key)
|
ticket = Ticket.objects.get(id=ticket_id, submitter_email__iexact=email, secret_key__iexact=key)
|
||||||
except (ObjectDoesNotExist, ValueError):
|
except (ObjectDoesNotExist, ValueError):
|
||||||
return search_for_ticket(request, _('Invalid ticket ID or e-mail address. Please try again.'))
|
return SearchForTicketView.as_view()(request, _('Invalid ticket ID or e-mail address. Please try again.'))
|
||||||
|
|
||||||
if 'close' in request.GET and ticket.status == Ticket.RESOLVED_STATUS:
|
if 'close' in request.GET and ticket.status == Ticket.RESOLVED_STATUS:
|
||||||
from helpdesk.update_ticket import update_ticket
|
from helpdesk.update_ticket import update_ticket
|
||||||
@ -247,6 +249,17 @@ class ViewTicket(TemplateView):
|
|||||||
return redirect_url
|
return redirect_url
|
||||||
|
|
||||||
|
|
||||||
|
class MyTickets(TemplateView):
|
||||||
|
template_name = 'helpdesk/my_tickets.html'
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return HttpResponseRedirect(reverse('helpdesk:login'))
|
||||||
|
|
||||||
|
context = self.get_context_data(**kwargs)
|
||||||
|
return self.render_to_response(context)
|
||||||
|
|
||||||
|
|
||||||
def change_language(request):
|
def change_language(request):
|
||||||
return_to = ''
|
return_to = ''
|
||||||
if 'return_to' in request.GET:
|
if 'return_to' in request.GET:
|
||||||
|
Loading…
Reference in New Issue
Block a user