Files
korrekturmanagementsystem/ticketsystem/views.py
2025-06-11 21:56:44 +02:00

380 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 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 AssignedTicketListView(LoginRequiredMixin, ListView):
model = Ticket
template_name = "ticketsystem/assigned_tickets.html"
context_object_name = "tickets"
ordering = ["-created_at"]
def get_queryset(self):
return Ticket.objects.filter(assigned_to=self.request.user).exclude(
status="closed"
) # oder "geschlossen", je nach Wahl
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