Skip to content

Commit

Permalink
Merge pull request #1143 from timthelion/webhooks
Browse files Browse the repository at this point in the history
Add my tickets view for logged in non-staff users
  • Loading branch information
uhurusurfa committed Nov 27, 2023
2 parents 761b91d + 2360c2e commit 6f1749a
Show file tree
Hide file tree
Showing 16 changed files with 396 additions and 196 deletions.
3 changes: 0 additions & 3 deletions demo/demodesk/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,6 @@
# Allow users to change their passwords
HELPDESK_SHOW_CHANGE_PASSWORD = True

# Activate the API
HELPDESK_ACTIVATE_API_ENDPOINT = True

# Instead of showing the public web portal first,
# we can instead redirect users straight to the login page.
HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT = False
Expand Down
4 changes: 0 additions & 4 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ API
A REST API (built with ``djangorestframework``) is available in order to list, create, update and delete tickets from
other tools thanks to HTTP requests.

If you wish to use it, you have to add this line in your settings::

HELPDESK_ACTIVATE_API_ENDPOINT = True

You must be authenticated to access the API, the URL endpoint is ``/api/tickets/``.
You can configure how you wish to authenticate to the API by customizing the ``DEFAULT_AUTHENTICATION_CLASSES`` key
in the ``REST_FRAMEWORK`` setting (more information on this page : https://www.django-rest-framework.org/api-guide/authentication/)
Expand Down
5 changes: 0 additions & 5 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,6 @@ Staff Ticket Creation Settings

**Default:** ``HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO = False``

- **HELPDESK_ACTIVATE_API_ENDPOINT** Activate the API endpoint to manage tickets thanks to Django REST Framework. See the API section in documentation for more information.

**Default:** ``HELPDESK_ACTIVATE_API_ENDPOINT = False``


Staff Ticket View Settings
------------------------------

Expand Down
202 changes: 101 additions & 101 deletions helpdesk/fixtures/emailtemplate.json

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions helpdesk/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,46 @@ def get_kbitem(self, obj):
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 Meta:
model = FollowUpAttachment
Expand Down
4 changes: 0 additions & 4 deletions helpdesk/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,6 @@
HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO = getattr(
settings, 'HELPDESK_CREATE_TICKET_HIDE_ASSIGNED_TO', False)

# Activate the API endpoint to manage tickets thanks to Django REST Framework
HELPDESK_ACTIVATE_API_ENDPOINT = getattr(
settings, 'HELPDESK_ACTIVATE_API_ENDPOINT', False)


#################
# email options #
Expand Down
65 changes: 65 additions & 0 deletions helpdesk/templates/helpdesk/my_tickets.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{% 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>
</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>
</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 %}
5 changes: 5 additions & 0 deletions helpdesk/templates/helpdesk/navigation-header.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@
</li>
{% endif %}
{% if not request.path == '/helpdesk/login/' or user.is_authenticated %}
<div class="nav-item">
<div class="nav-link">
{{user.username}}
</div>
</div>
<li class="nav-item dropdown no-arrow">
{% if user.is_authenticated %}
<a class="nav-link dropdown-toggle" href="{% url 'helpdesk:logout' %}" id="userDropdown" role="button" aria-haspopup="true" aria-expanded="false"><i class="fas fa-fw fa-sign-out-alt"></i> {% trans "Logout" %}</a>
Expand Down
15 changes: 15 additions & 0 deletions helpdesk/templates/helpdesk/navigation-sidebar.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
<span>{% trans "New Ticket" %}</span>
</a>
</li>
<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>
<li class="nav-item{% if 'reports' in request.path %} active{% endif %}">
<a class="nav-link" href="{% url 'helpdesk:report_index' %}">
<i class="fas fa-fw fa-chart-area"></i>
Expand Down Expand Up @@ -68,6 +74,15 @@
<span>{% trans "New Ticket" %}</span>
</a>
</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 %}
<li class="nav-item{% if 'kb' in request.path %} active{% endif %}">
<a class="nav-link" href="{% url 'helpdesk:kb_index' %}">
Expand Down
48 changes: 48 additions & 0 deletions helpdesk/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,3 +359,51 @@ def test_create_follow_up_with_attachments(self):
created_followup.followupattachment_set.last().filename, 'file.jpg')
self.assertEqual(
created_followup.followupattachment_set.last().mime_type, 'image/jpg')


class UserTicketTest(APITestCase):
def setUp(self):
self.queue = Queue.objects.create(title='Test queue')
self.user = User.objects.create_user(username='test')
self.client.force_authenticate(self.user)

def test_get_user_tickets(self):
user = User.objects.create_user(username='test2', email="foo@example.com")
ticket_1 = Ticket.objects.create(
queue=self.queue, title='Test 1',
submitter_email="foo@example.com")
ticket_2 = Ticket.objects.create(
queue=self.queue, title='Test 2',
submitter_email="bar@example.com")
ticket_3 = Ticket.objects.create(
queue=self.queue, title='Test 3',
submitter_email="foo@example.com")
self.client.force_authenticate(user)
response = self.client.get('/api/user_tickets/')
self.assertEqual(response.status_code, HTTP_200_OK)
self.assertEqual(len(response.data["results"]), 2)
self.assertEqual(response.data["results"][0]['id'], ticket_3.id)
self.assertEqual(response.data["results"][1]['id'], ticket_1.id)

def test_staff_user(self):
staff_user = User.objects.create_user(username='test2', is_staff=True, email="staff@example.com")
ticket_1 = Ticket.objects.create(
queue=self.queue, title='Test 1',
submitter_email="staff@example.com")
ticket_2 = Ticket.objects.create(
queue=self.queue, title='Test 2',
submitter_email="foo@example.com")
self.client.force_authenticate(staff_user)
response = self.client.get('/api/user_tickets/')
self.assertEqual(response.status_code, HTTP_200_OK)
self.assertEqual(len(response.data["results"]), 1)

def test_not_logged_in_user(self):
ticket_1 = Ticket.objects.create(
queue=self.queue, title='Test 1',
submitter_email="ex@example.com")
self.client.logout()
response = self.client.get('/api/user_tickets/')
self.assertEqual(response.status_code, HTTP_403_FORBIDDEN)


26 changes: 13 additions & 13 deletions helpdesk/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from helpdesk import settings as helpdesk_settings
from helpdesk.decorators import helpdesk_staff_member_required, protect_view
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


Expand Down Expand Up @@ -154,18 +154,19 @@ def get_context_data(self, **kwargs):

urlpatterns += [
path("", protect_view(public.Homepage.as_view()), name="home"),
path("tickets/my-tickets/", protect_view(public.MyTickets.as_view()), name="my-tickets"),
path("tickets/submit/", public.create_ticket, name="submit"),
path(
"tickets/submit_iframe/",
public.CreateTicketIframeView.as_view(),
protect_view(public.CreateTicketIframeView.as_view()),
name="submit_iframe",
),
path(
"tickets/success_iframe/", # Ticket was submitted successfully
public.SuccessIframeView.as_view(),
protect_view(public.SuccessIframeView.as_view()),
name="success_iframe",
),
path("view/", public.view_ticket, name="public_view"),
path("view/", protect_view(public.ViewTicket.as_view()), name="public_view"),
path("change_language/", public.change_language,
name="public_change_language"),
]
Expand Down Expand Up @@ -199,15 +200,14 @@ def get_context_data(self, **kwargs):
]


# API is added to url conf based on the setting (False by default)
if helpdesk_settings.HELPDESK_ACTIVATE_API_ENDPOINT:
router = DefaultRouter()
router.register(r"tickets", TicketViewSet, basename="ticket")
router.register(r"followups", FollowUpViewSet, basename="followups")
router.register(r"followups-attachments",
FollowUpAttachmentViewSet, basename="followupattachments")
router.register(r"users", CreateUserView, basename="user")
urlpatterns += [re_path(r"^api/", include(router.urls))]
router = DefaultRouter()
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-attachments",
FollowUpAttachmentViewSet, basename="followupattachments")
router.register(r"users", CreateUserView, basename="user")
urlpatterns += [re_path(r"^api/", include(router.urls))]


urlpatterns += [
Expand Down
27 changes: 25 additions & 2 deletions helpdesk/views/api.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
from django.contrib.auth import get_user_model
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.mixins import CreateModelMixin
from rest_framework.permissions import IsAdminUser
from rest_framework.permissions import IsAdminUser, IsAuthenticated
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
permission_classes = [IsAuthenticated]

def get_queryset(self):
return Ticket.objects.filter(submitter_email=self.request.user.email).order_by('-created')


class TicketViewSet(viewsets.ModelViewSet):
Expand All @@ -13,6 +33,7 @@ class TicketViewSet(viewsets.ModelViewSet):
"""
queryset = Ticket.objects.all()
serializer_class = TicketSerializer
pagination_class = ConservativePagination
permission_classes = [IsAdminUser]

def get_queryset(self):
Expand All @@ -30,12 +51,14 @@ def get_object(self):
class FollowUpViewSet(viewsets.ModelViewSet):
queryset = FollowUp.objects.all()
serializer_class = FollowUpSerializer
pagination_class = ConservativePagination
permission_classes = [IsAdminUser]


class FollowUpAttachmentViewSet(viewsets.ModelViewSet):
queryset = FollowUpAttachment.objects.all()
serializer_class = FollowUpAttachmentSerializer
pagination_class = ConservativePagination
permission_classes = [IsAdminUser]


Expand Down

0 comments on commit 6f1749a

Please sign in to comment.