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", "
"), 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