374 lines
13 KiB
Python
374 lines
13 KiB
Python
from django.contrib import messages
|
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
|
from django.db.models import Q
|
|
from django.http import HttpResponse
|
|
from django.shortcuts import redirect, render
|
|
from django.urls import reverse, reverse_lazy
|
|
from django.utils import timezone
|
|
from django.views.generic import ListView, TemplateView
|
|
from django.views.generic.edit import CreateView, UpdateView
|
|
from reportlab.lib.enums import TA_CENTER, TA_LEFT
|
|
from reportlab.lib.pagesizes import A4
|
|
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
|
from reportlab.lib.units import cm
|
|
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer
|
|
|
|
from .forms import CommentForm, TicketForm
|
|
from .models import FAQ, Course, Ticket, TicketHistory
|
|
|
|
|
|
class HomeView(TemplateView):
|
|
template_name = "ticketsystem/home.html"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context.update(
|
|
{
|
|
"total_tickets": Ticket.objects.count(),
|
|
"open_tickets": Ticket.objects.filter(status="open").count(),
|
|
"closed_tickets": Ticket.objects.filter(status="closed").count(),
|
|
"recent_tickets": Ticket.objects.order_by("-updated_at")[:5],
|
|
}
|
|
)
|
|
return context
|
|
|
|
|
|
class TicketListView(ListView):
|
|
model = Ticket
|
|
template_name = "ticketsystem/index.html"
|
|
context_object_name = "tickets"
|
|
ordering = ["-created_at"]
|
|
paginate_by = 10
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset()
|
|
status = self.request.GET.get("status")
|
|
assigned_to = self.request.GET.get("assigned_to")
|
|
query = self.request.GET.get("q")
|
|
course = self.request.GET.get("course")
|
|
|
|
if status:
|
|
queryset = queryset.filter(status=status)
|
|
if assigned_to:
|
|
queryset = queryset.filter(assigned_to_id=assigned_to)
|
|
if course: # NEU
|
|
queryset = queryset.filter(course_id=course)
|
|
if query:
|
|
queryset = queryset.filter(
|
|
Q(title__icontains=query) | Q(description__icontains=query)
|
|
)
|
|
return queryset
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
context["selected_status"] = self.request.GET.get("status", "")
|
|
context["selected_course"] = self.request.GET.get("course", "")
|
|
context["search_query"] = self.request.GET.get("q", "")
|
|
context["status_choices"] = Ticket.STATUS_CHOICES
|
|
context["courses"] = Course.objects.filter(is_active=True)
|
|
return context
|
|
|
|
|
|
class TicketDetailUpdateView(UpdateView):
|
|
model = Ticket
|
|
form_class = TicketForm # Verwende das custom Form anstatt fields
|
|
template_name = "ticketsystem/detail.html"
|
|
comment_form_class = CommentForm
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
self.can_edit = False
|
|
self.is_creator = False
|
|
self.is_tutor = False
|
|
|
|
def get_success_url(self):
|
|
return reverse("detail", kwargs={"pk": self.object.pk})
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
self.ticket = self.get_object()
|
|
user = request.user
|
|
|
|
# Erweiterte Berechtigungslogik
|
|
is_creator = user == self.ticket.created_by
|
|
is_assigned_tutor = user == self.ticket.assigned_to
|
|
is_superuser = user.is_superuser
|
|
|
|
# Bearbeitungsrechte abhängig vom Status
|
|
if is_superuser:
|
|
self.can_edit = True
|
|
elif self.ticket.status == "resolved" and is_creator:
|
|
self.can_edit = True
|
|
elif self.ticket.status == "closed":
|
|
self.can_edit = False
|
|
else:
|
|
self.can_edit = is_assigned_tutor
|
|
|
|
# Zusätzliche Flags für Template
|
|
self.is_creator = is_creator
|
|
self.is_tutor = is_assigned_tutor
|
|
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_form_kwargs(self):
|
|
"""Übergibt zusätzliche kwargs ans Form"""
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["user"] = self.request.user
|
|
kwargs["ticket"] = self.object
|
|
return kwargs
|
|
|
|
def get_context_data(self, **kwargs):
|
|
context = super().get_context_data(**kwargs)
|
|
# Füge Berechtigungs-Infos zum Context hinzu
|
|
context["is_creator"] = self.is_creator
|
|
context["is_tutor"] = self.is_tutor
|
|
context["can_edit"] = self.can_edit
|
|
|
|
# Kommentarformular hinzufügen
|
|
if "comment_form" not in context:
|
|
context["comment_form"] = self.comment_form_class()
|
|
return context
|
|
|
|
def form_valid(self, form):
|
|
ticket = form.instance
|
|
original = Ticket.objects.get(pk=ticket.pk)
|
|
|
|
# Alten assigned_to Wert merken für History
|
|
old_assigned_to = original.assigned_to
|
|
|
|
response = super().form_valid(form) # Speichert das Ticket
|
|
|
|
# History tracking für geänderte Felder
|
|
tracked_fields = [
|
|
"title",
|
|
"description",
|
|
"status",
|
|
"mistake",
|
|
"course",
|
|
"answer",
|
|
"material",
|
|
]
|
|
for field in tracked_fields:
|
|
if field in form.changed_data:
|
|
old_value = getattr(original, field)
|
|
new_value = form.cleaned_data.get(field)
|
|
|
|
# Für ForeignKey Felder den Display-Namen verwenden
|
|
if field == "status":
|
|
old_value = original.get_status_display()
|
|
new_value = ticket.get_status_display()
|
|
elif field == "mistake":
|
|
old_value = original.get_mistake_display()
|
|
new_value = ticket.get_mistake_display()
|
|
elif field == "course":
|
|
old_value = str(old_value)
|
|
new_value = str(new_value)
|
|
elif field == "answer":
|
|
if old_value:
|
|
old_value = (
|
|
old_value[:50] + "..." if len(old_value) > 50 else old_value
|
|
)
|
|
else:
|
|
old_value = "Keine Antwort"
|
|
if new_value:
|
|
new_value = (
|
|
new_value[:50] + "..." if len(new_value) > 50 else new_value
|
|
)
|
|
else:
|
|
new_value = "Keine Antwort"
|
|
|
|
TicketHistory.objects.create(
|
|
ticket=ticket,
|
|
changed_by=self.request.user,
|
|
field=field,
|
|
old_value=str(old_value),
|
|
new_value=str(new_value),
|
|
)
|
|
|
|
if "course" in form.changed_data and old_assigned_to != ticket.assigned_to:
|
|
old_name = old_assigned_to.username if old_assigned_to else "Niemand"
|
|
new_name = ticket.assigned_to.username if ticket.assigned_to else "Niemand"
|
|
TicketHistory.objects.create(
|
|
ticket=ticket,
|
|
changed_by=self.request.user,
|
|
field="assigned_to",
|
|
old_value=old_name,
|
|
new_value=new_name,
|
|
)
|
|
# Erweitere die changed_data Liste für die Nachricht
|
|
changed_fields = list(form.changed_data)
|
|
if "assigned_to" not in changed_fields:
|
|
changed_fields.append("assigned_to (automatisch)")
|
|
else:
|
|
changed_fields = form.changed_data
|
|
|
|
if changed_fields:
|
|
messages.success(
|
|
self.request,
|
|
f"Ticket erfolgreich aktualisiert. Geänderte Felder: {', '.join(changed_fields)}",
|
|
)
|
|
return response
|
|
|
|
def post(self, request, *args, **kwargs):
|
|
self.object = self.get_object() # Wichtig: object setzen für beide Fälle
|
|
|
|
if "comment_submit" in request.POST:
|
|
# Kommentar absenden
|
|
comment_form = self.comment_form_class(request.POST)
|
|
if comment_form.is_valid():
|
|
comment = comment_form.save(commit=False)
|
|
comment.ticket = self.object
|
|
comment.author = request.user
|
|
comment.save()
|
|
messages.success(request, "Kommentar hinzugefügt.")
|
|
return redirect(self.get_success_url())
|
|
else:
|
|
# Kommentarformular fehlerhaft: Seite neu laden mit Fehlern
|
|
context = self.get_context_data(comment_form=comment_form)
|
|
return self.render_to_response(context)
|
|
else:
|
|
# Ticket bearbeiten (UpdateView standard)
|
|
if not self.can_edit:
|
|
messages.error(request, "⛔ Du darfst dieses Ticket nicht bearbeiten.")
|
|
return redirect(self.get_success_url())
|
|
return super().post(request, *args, **kwargs)
|
|
|
|
|
|
class TicketCreateView(CreateView):
|
|
model = Ticket
|
|
form_class = TicketForm
|
|
template_name = "ticketsystem/ticket_form.html"
|
|
|
|
def form_valid(self, form):
|
|
form.instance.created_by = self.request.user
|
|
form.instance.status = "new"
|
|
return super().form_valid(form)
|
|
|
|
def get_success_url(self):
|
|
return reverse("detail", kwargs={"pk": self.object.pk})
|
|
|
|
|
|
class TicketUpdateView(LoginRequiredMixin, UpdateView):
|
|
model = Ticket
|
|
fields = ["title", "description", "status", "mistake", "assigned_to", "course"]
|
|
template_name = "ticketsystem/ticket_form.html"
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
ticket = self.get_object()
|
|
user = request.user
|
|
if user != ticket.assigned_to and not user.is_staff:
|
|
messages.error(request, "⛔ Du darfst dieses Ticket nicht bearbeiten.")
|
|
return redirect("detail", pk=ticket.pk)
|
|
return super().dispatch(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
return Ticket.objects.all() # Optional: Nur eigene Tickets bearbeiten lassen?
|
|
|
|
def form_valid(self, form):
|
|
ticket = form.instance
|
|
original = Ticket.objects.get(pk=ticket.pk)
|
|
|
|
response = super().form_valid(form) # Speichert das Ticket
|
|
|
|
tracked_fields = ["status", "description", "mistake"]
|
|
for field in tracked_fields:
|
|
if field in form.changed_data:
|
|
old_value = getattr(original, field)
|
|
new_value = form.cleaned_data.get(field)
|
|
TicketHistory.objects.create(
|
|
ticket=ticket,
|
|
changed_by=self.request.user,
|
|
field=field,
|
|
old_value=str(old_value),
|
|
new_value=str(new_value),
|
|
)
|
|
return response
|
|
|
|
def get_success_url(self):
|
|
return reverse_lazy("detail", kwargs={"pk": self.object.pk})
|
|
|
|
|
|
def faq_list(request):
|
|
"""Zeigt alle aktiven FAQs an"""
|
|
faqs = FAQ.objects.filter(is_active=True)
|
|
return render(request, "ticketsystem/faq.html", {"faqs": faqs})
|
|
|
|
|
|
def faq_pdf_download(request):
|
|
"""Generiert PDF mit allen FAQs"""
|
|
|
|
current_date = timezone.now()
|
|
date_string = current_date.strftime("%Y-%m-%d")
|
|
date_display = current_date.strftime("%d.%m.%Y")
|
|
|
|
# Response Setup mit Datum im Dateinamen
|
|
response = HttpResponse(content_type="application/pdf")
|
|
response["Content-Disposition"] = (
|
|
f'attachment; filename="FAQ_Ticketsystem_{date_string}.pdf"'
|
|
)
|
|
|
|
# PDF erstellen
|
|
doc = SimpleDocTemplate(
|
|
response, pagesize=A4, topMargin=2 * cm, bottomMargin=2 * cm
|
|
)
|
|
|
|
# Styles
|
|
styles = getSampleStyleSheet()
|
|
title_style = ParagraphStyle(
|
|
"CustomTitle",
|
|
parent=styles["Heading1"],
|
|
fontSize=24,
|
|
textColor="#1a1a1a",
|
|
spaceAfter=20,
|
|
alignment=TA_CENTER,
|
|
)
|
|
subtitle_style = ParagraphStyle(
|
|
"Subtitle",
|
|
parent=styles["Normal"],
|
|
fontSize=12,
|
|
textColor="#666666",
|
|
spaceAfter=30,
|
|
alignment=TA_CENTER,
|
|
)
|
|
question_style = ParagraphStyle(
|
|
"Question",
|
|
parent=styles["Heading2"],
|
|
fontSize=14,
|
|
textColor="#2563eb",
|
|
spaceAfter=10,
|
|
)
|
|
answer_style = ParagraphStyle(
|
|
"Answer", parent=styles["Normal"], fontSize=11, spaceAfter=20, alignment=TA_LEFT
|
|
)
|
|
|
|
# Content
|
|
elements = []
|
|
|
|
# Titel
|
|
elements.append(Paragraph("Häufig gestellte Fragen (FAQ)", title_style))
|
|
|
|
elements.append(Paragraph(f"Stand: {date_display}", subtitle_style))
|
|
|
|
elements.append(Spacer(1, 20))
|
|
|
|
# FAQs holen
|
|
faqs = FAQ.objects.filter(is_active=True)
|
|
|
|
if faqs.exists():
|
|
# FAQs hinzufügen
|
|
for i, faq in enumerate(faqs, 1):
|
|
# Frage
|
|
elements.append(Paragraph(f"{i}. {faq.question}", question_style))
|
|
|
|
# Antwort
|
|
elements.append(Paragraph(faq.answer.replace("\n", "<br/>"), answer_style))
|
|
|
|
# Abstand zwischen FAQs
|
|
elements.append(Spacer(1, 10))
|
|
else:
|
|
elements.append(Paragraph("Derzeit sind keine FAQs verfügbar.", answer_style))
|
|
|
|
# PDF generieren
|
|
doc.build(elements)
|
|
|
|
return response
|