diff --git a/requirements.txt b/requirements.txt
index eca8ce8..00c4c0f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,6 +8,7 @@ Django==5.2
djlint==1.36.4
EditorConfig==0.17.0
gunicorn==23.0.0
+isort==6.0.1
jsbeautifier==1.15.4
json5==0.12.0
mypy_extensions==1.1.0
diff --git a/ticketsystem/static/js/faq.js b/ticketsystem/static/js/faq.js
new file mode 100644
index 0000000..5bb1832
--- /dev/null
+++ b/ticketsystem/static/js/faq.js
@@ -0,0 +1,33 @@
+function toggleFAQ(index) {
+ const content = document.getElementById(`content-${index}`);
+ const icon = document.getElementById(`icon-${index}`);
+
+ if (content.classList.contains('hidden')) {
+ content.classList.remove('hidden');
+ icon.classList.add('rotate-180');
+ } else {
+ content.classList.add('hidden');
+ icon.classList.remove('rotate-180');
+ }
+}
+
+function toggleAllFAQs() {
+ const button = document.getElementById('toggle-all-btn');
+ const allContents = document.querySelectorAll('[id^="content-"]');
+ const allIcons = document.querySelectorAll('[id^="icon-"]');
+
+ // Prüfen ob alle eingeklappt sind
+ const allCollapsed = Array.from(allContents).every(content => content.classList.contains('hidden'));
+
+ if (allCollapsed) {
+ // Alle ausklappen
+ allContents.forEach(content => content.classList.remove('hidden'));
+ allIcons.forEach(icon => icon.classList.add('rotate-180'));
+ button.innerHTML = '📁 Alle einklappen';
+ } else {
+ // Alle einklappen
+ allContents.forEach(content => content.classList.add('hidden'));
+ allIcons.forEach(icon => icon.classList.remove('rotate-180'));
+ button.innerHTML = '📂 Alle ausklappen';
+ }
+}
diff --git a/ticketsystem/templates/ticketsystem/faq.html b/ticketsystem/templates/ticketsystem/faq.html
index 736139b..bfe5abd 100644
--- a/ticketsystem/templates/ticketsystem/faq.html
+++ b/ticketsystem/templates/ticketsystem/faq.html
@@ -1,19 +1,46 @@
{% extends "ticketsystem/base.html" %}
+{% load static %}
{% block content %}
+
{% for faq in faqs %}
-
-
{{ forloop.counter }}. {{ faq.question }}
-
{{ faq.answer|linebreaks }}
+
+
+
+
+
+
+
{{ faq.answer|linebreaks }}
+
{% empty %}
@@ -21,4 +48,7 @@
{% endfor %}
-{% endblock %}
+
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/ticketsystem/views.py b/ticketsystem/views.py
index 14f33ae..89c42c4 100644
--- a/ticketsystem/views.py
+++ b/ticketsystem/views.py
@@ -1,22 +1,20 @@
-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.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 getSampleStyleSheet, ParagraphStyle
+from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import cm
-from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer
-from reportlab.lib.enums import TA_LEFT, TA_CENTER
+from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer
-from .models import Ticket, TicketHistory, FAQ, Course
+from .forms import CommentForm, TicketForm
+from .models import FAQ, Course, Ticket, TicketHistory
class HomeView(TemplateView):
@@ -92,9 +90,9 @@ class TicketDetailUpdateView(UpdateView):
# Bearbeitungsrechte abhängig vom Status
if is_superuser:
self.can_edit = True
- elif self.ticket.status == 'resolved' and is_creator:
+ elif self.ticket.status == "resolved" and is_creator:
self.can_edit = True
- elif self.ticket.status == 'closed':
+ elif self.ticket.status == "closed":
self.can_edit = False
else:
self.can_edit = is_assigned_tutor
@@ -108,16 +106,16 @@ class TicketDetailUpdateView(UpdateView):
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
+ 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
+ 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:
@@ -134,7 +132,15 @@ class TicketDetailUpdateView(UpdateView):
response = super().form_valid(form) # Speichert das Ticket
# History tracking für geänderte Felder
- tracked_fields = ["title", "description", "status", "mistake", "course", "answer", "material"]
+ 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)
@@ -152,11 +158,15 @@ class TicketDetailUpdateView(UpdateView):
new_value = str(new_value)
elif field == "answer":
if old_value:
- old_value = old_value[:50] + "..." if len(old_value) > 50 else 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
+ new_value = (
+ new_value[:50] + "..." if len(new_value) > 50 else new_value
+ )
else:
new_value = "Keine Antwort"
@@ -286,41 +296,54 @@ class TicketUpdateView(LoginRequiredMixin, UpdateView):
def faq_list(request):
"""Zeigt alle aktiven FAQs an"""
faqs = FAQ.objects.filter(is_active=True)
- return render(request, 'ticketsystem/faq.html', {'faqs': faqs})
+ 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"'
+
+ 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)
+ doc = SimpleDocTemplate(
+ response, pagesize=A4, topMargin=2 * cm, bottomMargin=2 * cm
+ )
# Styles
styles = getSampleStyleSheet()
title_style = ParagraphStyle(
- 'CustomTitle',
- parent=styles['Heading1'],
+ "CustomTitle",
+ parent=styles["Heading1"],
fontSize=24,
- textColor='#1a1a1a',
+ textColor="#1a1a1a",
+ spaceAfter=20,
+ alignment=TA_CENTER,
+ )
+ subtitle_style = ParagraphStyle(
+ "Subtitle",
+ parent=styles["Normal"],
+ fontSize=12,
+ textColor="#666666",
spaceAfter=30,
- alignment=TA_CENTER
+ alignment=TA_CENTER,
)
question_style = ParagraphStyle(
- 'Question',
- parent=styles['Heading2'],
+ "Question",
+ parent=styles["Heading2"],
fontSize=14,
- textColor='#2563eb',
- spaceAfter=10
+ textColor="#2563eb",
+ spaceAfter=10,
)
answer_style = ParagraphStyle(
- 'Answer',
- parent=styles['Normal'],
- fontSize=11,
- spaceAfter=20,
- alignment=TA_LEFT
+ "Answer", parent=styles["Normal"], fontSize=11, spaceAfter=20, alignment=TA_LEFT
)
# Content
@@ -328,23 +351,29 @@ def faq_pdf_download(request):
# 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)
- # FAQs hinzufügen
- for i, faq in enumerate(faqs, 1):
- # Frage
- elements.append(Paragraph(f"{i}. {faq.question}", question_style))
+ 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))
+ # Antwort
+ elements.append(Paragraph(faq.answer.replace("\n", "
"), answer_style))
- # Abstand zwischen FAQs
- elements.append(Spacer(1, 10))
+ # 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
\ No newline at end of file
+ return response