feat: added faqs
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Ticket, Course
|
from .models import Ticket, Course, FAQ
|
||||||
|
|
||||||
admin.site.register(Ticket)
|
admin.site.register(Ticket)
|
||||||
admin.site.register(Course)
|
admin.site.register(Course)
|
||||||
|
admin.site.register(FAQ)
|
||||||
|
|||||||
@@ -95,3 +95,19 @@ class TicketHistory(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ["-changed_at"]
|
ordering = ["-changed_at"]
|
||||||
|
|
||||||
|
|
||||||
|
class FAQ(models.Model):
|
||||||
|
"""Einfaches FAQ Model"""
|
||||||
|
question = models.CharField(max_length=300, verbose_name="Frage")
|
||||||
|
answer = models.TextField(verbose_name="Antwort")
|
||||||
|
order = models.IntegerField(default=0, verbose_name="Reihenfolge")
|
||||||
|
is_active = models.BooleanField(default=True, verbose_name="Aktiv")
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['order', 'question']
|
||||||
|
verbose_name = "FAQ"
|
||||||
|
verbose_name_plural = "FAQs"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.question
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
<a href="{% url 'ticket-list' %}" class="text-white hover:text-gray-300">📋 Tickets</a>
|
<a href="{% url 'ticket-list' %}" class="text-white hover:text-gray-300">📋 Tickets</a>
|
||||||
<a href="{% url 'assigned-tickets' %}"
|
<a href="{% url 'assigned-tickets' %}"
|
||||||
class="text-white hover:text-gray-300">🧾 Meine Tickets</a>
|
class="text-white hover:text-gray-300">🧾 Meine Tickets</a>
|
||||||
|
<a href="{% url 'faq-list' %}" class="text-white hover:text-gray-300">❓ FAQ</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-4">
|
||||||
{% if user.is_authenticated %}
|
{% if user.is_authenticated %}
|
||||||
@@ -30,8 +31,7 @@
|
|||||||
</nav>
|
</nav>
|
||||||
<!-- Global Container -->
|
<!-- Global Container -->
|
||||||
<div class="max-w-4xl mx-auto mt-8 px-4">
|
<div class="max-w-4xl mx-auto mt-8 px-4">
|
||||||
{% block content %}
|
{% block content %}{% endblock %}
|
||||||
{% endblock %}
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
24
ticketsystem/templates/ticketsystem/faq.html
Normal file
24
ticketsystem/templates/ticketsystem/faq.html
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{% extends "ticketsystem/base.html" %}
|
||||||
|
{% block content %}
|
||||||
|
<div class="max-w-4xl mx-auto px-4 py-6">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="bg-white rounded-lg shadow p-6 mb-6">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<h1 class="text-3xl font-bold">❓ Häufig gestellte Fragen</h1>
|
||||||
|
<a href="{% url 'faq-pdf-download' %}"
|
||||||
|
class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">📄 PDF Download</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- FAQ Liste -->
|
||||||
|
{% for faq in faqs %}
|
||||||
|
<div class="bg-white rounded-lg shadow p-6 mb-4">
|
||||||
|
<h3 class="font-bold text-lg mb-2">{{ forloop.counter }}. {{ faq.question }}</h3>
|
||||||
|
<div class="text-gray-700">{{ faq.answer|linebreaks }}</div>
|
||||||
|
</div>
|
||||||
|
{% empty %}
|
||||||
|
<div class="bg-white rounded-lg shadow p-8 text-center">
|
||||||
|
<p class="text-gray-500">Noch keine FAQs vorhanden.</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -7,6 +7,8 @@ from .views import (
|
|||||||
HomeView,
|
HomeView,
|
||||||
AssignedTicketListView,
|
AssignedTicketListView,
|
||||||
TicketDetailUpdateView,
|
TicketDetailUpdateView,
|
||||||
|
faq_list,
|
||||||
|
faq_pdf_download
|
||||||
)
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@@ -20,4 +22,6 @@ urlpatterns = [
|
|||||||
path("new/", TicketCreateView.as_view(), name="create"),
|
path("new/", TicketCreateView.as_view(), name="create"),
|
||||||
path("<int:pk>/modify/", TicketUpdateView.as_view(), name="modify"),
|
path("<int:pk>/modify/", TicketUpdateView.as_view(), name="modify"),
|
||||||
path("meine-tickets/", AssignedTicketListView.as_view(), name="assigned-tickets"),
|
path("meine-tickets/", AssignedTicketListView.as_view(), name="assigned-tickets"),
|
||||||
|
path('faq/', faq_list, name='faq-list'),
|
||||||
|
path('faq/download/', faq_pdf_download, name='faq-pdf-download'),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,10 +7,16 @@ from .forms import CommentForm, TicketForm
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect, render
|
||||||
from django.db.models import Q
|
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
|
from .models import Ticket, TicketHistory, FAQ
|
||||||
|
|
||||||
|
|
||||||
class HomeView(TemplateView):
|
class HomeView(TemplateView):
|
||||||
@@ -239,3 +245,70 @@ class TicketUpdateView(LoginRequiredMixin, UpdateView):
|
|||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
return reverse_lazy("detail", kwargs={"pk": self.object.pk})
|
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', '<br/>'), answer_style))
|
||||||
|
|
||||||
|
# Abstand zwischen FAQs
|
||||||
|
elements.append(Spacer(1, 10))
|
||||||
|
|
||||||
|
# PDF generieren
|
||||||
|
doc.build(elements)
|
||||||
|
|
||||||
|
return response
|
||||||
Reference in New Issue
Block a user