from django.views.generic import ListView, TemplateView from django.views.generic.edit import CreateView, UpdateView from django.urls import reverse_lazy from django.views.generic.detail import DetailView from django.views.generic.edit import FormMixin from .forms import CommentForm, TicketForm from django.urls import reverse from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib import messages from django.shortcuts import redirect, render from django.db.models import Q from django.http import HttpResponse from reportlab.lib.pagesizes import A4 from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle from reportlab.lib.units import cm from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer from reportlab.lib.enums import TA_LEFT, TA_CENTER from .models import Ticket, TicketHistory, FAQ 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") if status: queryset = queryset.filter(status=status) if assigned_to: queryset = queryset.filter(assigned_to_id=assigned_to) 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["search_query"] = self.request.GET.get("q", "") context["status_choices"] = Ticket.STATUS_CHOICES 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 self.can_edit = is_assigned_tutor or is_superuser # 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", "priority", "course", "answer"] 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 == "priority": old_value = original.get_priority_display() new_value = ticket.get_priority_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", "priority", "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", "priority"] 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""" # Response Setup response = HttpResponse(content_type='application/pdf') response['Content-Disposition'] = 'attachment; filename="FAQ_Ticketsystem.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=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(Spacer(1, 20)) # FAQs holen faqs = FAQ.objects.filter(is_active=True) # 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)) # PDF generieren doc.build(elements) return response