2025-03-22 15:29:19 -10:00

298 lines
11 KiB
Python

"""
django-helpdesk - A Django powered ticket tracker for small enterprise.
(c) Copyright 2008 Jutda. All Rights Reserved. See LICENSE for details.
views/public.py - All public facing views, eg non-staff (no authentication
required) views.
"""
from django.conf import settings
from django.core.exceptions import (
ImproperlyConfigured,
ObjectDoesNotExist,
PermissionDenied,
)
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.urls import reverse
from django.utils.translation import gettext as _
from django.views.decorators.clickjacking import xframe_options_exempt
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView
from helpdesk import settings as helpdesk_settings
from helpdesk.decorators import is_helpdesk_staff, protect_view
from helpdesk.lib import text_is_spam
from helpdesk.models import Queue, Ticket, UserSettings
from helpdesk.user import huser_from_request
import helpdesk.views.abstract_views as abstract_views
import helpdesk.views.staff as staff
from importlib import import_module
import logging
from urllib.parse import quote
logger = logging.getLogger(__name__)
def create_ticket(request, *args, **kwargs):
if is_helpdesk_staff(request.user):
return staff.CreateTicketView.as_view()(request, *args, **kwargs)
else:
return protect_view(CreateTicketView.as_view())(request, *args, **kwargs)
class BaseCreateTicketView(abstract_views.AbstractCreateTicketMixin, FormView):
def get_form_class(self):
try:
the_module, the_form_class = (
helpdesk_settings.HELPDESK_PUBLIC_TICKET_FORM_CLASS.rsplit(".", 1)
)
the_module = import_module(the_module)
the_form_class = getattr(the_module, the_form_class)
except Exception as e:
raise ImproperlyConfigured(
f"Invalid custom form class {helpdesk_settings.HELPDESK_PUBLIC_TICKET_FORM_CLASS}"
) from e
return the_form_class
def dispatch(self, *args, **kwargs):
request = self.request
if (
not request.user.is_authenticated
and helpdesk_settings.HELPDESK_REDIRECT_TO_LOGIN_BY_DEFAULT
):
return HttpResponseRedirect(reverse("login"))
if is_helpdesk_staff(request.user) or (
request.user.is_authenticated
and helpdesk_settings.HELPDESK_ALLOW_NON_STAFF_TICKET_UPDATE
):
try:
if request.user.usersettings_helpdesk.login_view_ticketlist:
return HttpResponseRedirect(reverse("helpdesk:list"))
else:
return HttpResponseRedirect(reverse("helpdesk:dashboard"))
except UserSettings.DoesNotExist:
return HttpResponseRedirect(reverse("helpdesk:dashboard"))
return super().dispatch(*args, **kwargs)
def get_initial(self):
initial_data = super().get_initial()
# add pre-defined data for public ticket
if hasattr(settings, "HELPDESK_PUBLIC_TICKET_QUEUE"):
# get the requested queue; return an error if queue not found
try:
initial_data["queue"] = Queue.objects.get(
slug=settings.HELPDESK_PUBLIC_TICKET_QUEUE,
allow_public_submission=True,
).id
except Queue.DoesNotExist as e:
logger.fatal(
"Public queue '%s' is configured as default but can't be found",
settings.HELPDESK_PUBLIC_TICKET_QUEUE,
)
raise ImproperlyConfigured("Wrong public queue configuration") from e
if hasattr(settings, "HELPDESK_PUBLIC_TICKET_PRIORITY"):
initial_data["priority"] = settings.HELPDESK_PUBLIC_TICKET_PRIORITY
if hasattr(settings, "HELPDESK_PUBLIC_TICKET_DUE_DATE"):
initial_data["due_date"] = settings.HELPDESK_PUBLIC_TICKET_DUE_DATE
return initial_data
def get_form_kwargs(self, *args, **kwargs):
kwargs = super().get_form_kwargs(*args, **kwargs)
if "_hide_fields_" in self.request.GET:
kwargs["hidden_fields"] = self.request.GET.get("_hide_fields_", "").split(
","
)
kwargs["readonly_fields"] = self.request.GET.get("_readonly_fields_", "").split(
","
)
return kwargs
def form_valid(self, form):
request = self.request
if text_is_spam(form.cleaned_data["body"], request):
# This submission is spam. Let's not save it.
return render(request, template_name="helpdesk/public_spam.html")
else:
ticket = form.save(
user=self.request.user if self.request.user.is_authenticated else None
)
try:
return HttpResponseRedirect(
"%s?ticket=%s&email=%s&key=%s"
% (
reverse("helpdesk:public_view"),
ticket.ticket_for_url,
quote(ticket.submitter_email),
ticket.secret_key,
)
)
except ValueError:
# if someone enters a non-int string for the ticket
return HttpResponseRedirect(reverse("helpdesk:home"))
class CreateTicketIframeView(BaseCreateTicketView):
template_name = "helpdesk/public_create_ticket_iframe.html"
@csrf_exempt
@xframe_options_exempt
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
def form_valid(self, form):
if super().form_valid(form).status_code == 302:
return HttpResponseRedirect(reverse("helpdesk:success_iframe"))
class SuccessIframeView(TemplateView):
template_name = "helpdesk/success_iframe.html"
@xframe_options_exempt
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)
class CreateTicketView(BaseCreateTicketView):
template_name = "helpdesk/public_create_ticket.html"
def get_form(self, form_class=None):
form = super().get_form(form_class)
# Add the CSS error class to the form in order to better see them in
# the page
form.error_css_class = "text-danger"
return form
class Homepage(CreateTicketView):
template_name = "helpdesk/public_homepage.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["kb_categories"] = huser_from_request(
self.request
).get_allowed_kb_categories()
return context
class SearchForTicketView(TemplateView):
template_name = "helpdesk/public_view_form.html"
def get(self, request, *args, **kwargs):
if (
hasattr(settings, "HELPDESK_VIEW_A_TICKET_PUBLIC")
and settings.HELPDESK_VIEW_A_TICKET_PUBLIC
):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
else:
raise PermissionDenied(
"Public viewing of tickets without a secret key is forbidden."
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
request = self.request
email = request.GET.get("email", None)
error_message = kwargs.get("error_message", None)
context.update(
{
"ticket": False,
"email": email,
"error_message": error_message,
"helpdesk_settings": helpdesk_settings,
}
)
return context
class ViewTicket(TemplateView):
template_name = "helpdesk/public_view_ticket.html"
def get(self, request, *args, **kwargs):
ticket_req = request.GET.get("ticket", None)
email = request.GET.get("email", None)
key = request.GET.get("key", "")
if not (ticket_req and email):
if ticket_req is None and email is None:
return SearchForTicketView.as_view()(request)
else:
return SearchForTicketView.as_view()(
request, _("Missing ticket ID or e-mail address. Please try again.")
)
try:
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)
elif (
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)
else:
ticket = Ticket.objects.get(
id=ticket_id, submitter_email__iexact=email, secret_key__iexact=key
)
except (ObjectDoesNotExist, ValueError):
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:
from helpdesk.update_ticket import update_ticket
update_ticket(
request.user,
ticket,
public=True,
comment=_("Submitter accepted resolution and closed ticket"),
new_status=Ticket.CLOSED_STATUS,
)
return HttpResponseRedirect(ticket.ticket_url)
# Prepare context for rendering
context = {
"key": key,
"mail": email,
"ticket": ticket,
"helpdesk_settings": helpdesk_settings,
"next": self.get_next_url(ticket_id),
}
return self.render_to_response(context)
def get_next_url(self, ticket_id):
redirect_url = ""
if is_helpdesk_staff(self.request.user):
redirect_url = reverse("helpdesk:view", args=[ticket_id])
if "close" in self.request.GET:
redirect_url += "?close"
elif helpdesk_settings.HELPDESK_NAVIGATION_ENABLED:
redirect_url = reverse("helpdesk:view", args=[ticket_id])
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):
return_to = ""
if "return_to" in request.GET:
return_to = request.GET["return_to"]
return render(request, "helpdesk/public_change_language.html", {"next": return_to})