fix: black formatting

This commit is contained in:
2025-06-11 21:57:28 +02:00
parent 8af0bbdb37
commit 272415d219
3 changed files with 85 additions and 64 deletions

View File

@@ -17,45 +17,55 @@ class CommentForm(forms.ModelForm):
class TicketForm(forms.ModelForm): class TicketForm(forms.ModelForm):
# Zentrale Definition der Status-Übergänge # Zentrale Definition der Status-Übergänge
STATUS_TRANSITIONS = { STATUS_TRANSITIONS = {
'tutor': { "tutor": {
'new': ['in_progress'], "new": ["in_progress"],
'in_progress': ['resolved', 'new'], "in_progress": ["resolved", "new"],
'resolved': ['closed'], "resolved": ["closed"],
'closed': [], "closed": [],
}, },
'creator': { "creator": {
'new': [], "new": [],
'in_progress': [], "in_progress": [],
'resolved': ['closed', 'new'], "resolved": ["closed", "new"],
'closed': [], "closed": [],
}, },
'superuser': { "superuser": {
# Superuser können alle Übergänge machen # Superuser können alle Übergänge machen
'new': ['in_progress', 'resolved', 'closed'], "new": ["in_progress", "resolved", "closed"],
'in_progress': ['new', 'resolved', 'closed'], "in_progress": ["new", "resolved", "closed"],
'resolved': ['new', 'in_progress', 'closed'], "resolved": ["new", "in_progress", "closed"],
'closed': ['new', 'in_progress', 'resolved'], "closed": ["new", "in_progress", "resolved"],
} },
} }
# Zentrale Definition welche Felder wann required sind # Zentrale Definition welche Felder wann required sind
REQUIRED_FIELDS_BY_STATUS = { REQUIRED_FIELDS_BY_STATUS = {
'resolved': ['answer'], # Answer required when resolving "resolved": ["answer"], # Answer required when resolving
} }
class Meta: class Meta:
model = Ticket model = Ticket
fields = ["title", "description", "status", "mistake", "course", "answer", "material"] fields = [
"title",
"description",
"status",
"mistake",
"course",
"answer",
"material",
]
widgets = { widgets = {
'answer': forms.Textarea(attrs={ "answer": forms.Textarea(
'rows': 4, attrs={
'placeholder': 'Beschreibe die Lösung des Problems...' "rows": 4,
}) "placeholder": "Beschreibe die Lösung des Problems...",
}
)
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None) self.user = kwargs.pop("user", None)
self.ticket = kwargs.pop('ticket', None) self.ticket = kwargs.pop("ticket", None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.ticket and self.user: if self.ticket and self.user:
@@ -79,17 +89,17 @@ class TicketForm(forms.ModelForm):
if is_superuser: if is_superuser:
return return
if self.ticket.status == 'resolved' and is_creator: if self.ticket.status == "resolved" and is_creator:
for field_name in self.fields: for field_name in self.fields:
if field_name == "answer": if field_name == "answer":
self.fields[field_name].disabled = True self.fields[field_name].disabled = True
elif is_tutor: elif is_tutor:
# Tutor darf ändern: # Tutor darf ändern:
readonly_fields = ['title', 'description', 'material'] readonly_fields = ["title", "description", "material"]
for field_name in readonly_fields: for field_name in readonly_fields:
if field_name in self.fields: if field_name in self.fields:
self.fields[field_name].disabled = True self.fields[field_name].disabled = True
elif is_creator and self.ticket.status != 'resolved': elif is_creator and self.ticket.status != "resolved":
for field_name in self.fields: for field_name in self.fields:
self.fields[field_name].disabled = True self.fields[field_name].disabled = True
@@ -99,11 +109,11 @@ class TicketForm(forms.ModelForm):
# Superuser bekommen alle Status-Optionen # Superuser bekommen alle Status-Optionen
if is_superuser: if is_superuser:
role = 'superuser' role = "superuser"
elif is_tutor: elif is_tutor:
role = 'tutor' role = "tutor"
elif is_creator: elif is_creator:
role = 'creator' role = "creator"
else: else:
role = None role = None
@@ -114,10 +124,9 @@ class TicketForm(forms.ModelForm):
allowed_statuses = [current_status] allowed_statuses = [current_status]
# Status-Choices filtern # Status-Choices filtern
all_choices = list(self.fields['status'].choices) all_choices = list(self.fields["status"].choices)
self.fields['status'].choices = [ self.fields["status"].choices = [
choice for choice in all_choices choice for choice in all_choices if choice[0] in allowed_statuses
if choice[0] in allowed_statuses
] ]
def _get_allowed_transitions(self, from_status, role): def _get_allowed_transitions(self, from_status, role):
@@ -137,7 +146,7 @@ class TicketForm(forms.ModelForm):
def clean(self): def clean(self):
cleaned_data = super().clean() cleaned_data = super().clean()
status = cleaned_data.get('status') status = cleaned_data.get("status")
if not self.ticket or not status: if not self.ticket or not status:
return cleaned_data return cleaned_data
@@ -150,27 +159,31 @@ class TicketForm(forms.ModelForm):
# Superuser dürfen alle Übergänge # Superuser dürfen alle Übergänge
if is_superuser: if is_superuser:
role = 'superuser' role = "superuser"
elif is_tutor: elif is_tutor:
role = 'tutor' role = "tutor"
elif is_creator: elif is_creator:
role = 'creator' role = "creator"
else: else:
role = None role = None
if role and not self._is_transition_allowed(old_status, status, role): if role and not self._is_transition_allowed(old_status, status, role):
raise ValidationError({ raise ValidationError(
'status': f'Übergang von "{self.ticket.get_status_display()}" zu "{dict(self.fields["status"].choices)[status]}" ist nicht erlaubt.' {
}) "status": f'Übergang von "{self.ticket.get_status_display()}" zu "{dict(self.fields["status"].choices)[status]}" ist nicht erlaubt.'
}
)
# Prüfe required fields für neuen Status # Prüfe required fields für neuen Status
required_fields = self._get_required_fields_for_status(status) required_fields = self._get_required_fields_for_status(status)
for field_name in required_fields: for field_name in required_fields:
if not cleaned_data.get(field_name): if not cleaned_data.get(field_name):
field_label = self.fields[field_name].label field_label = self.fields[field_name].label
raise ValidationError({ raise ValidationError(
field_name: f'{field_label} ist erforderlich, wenn der Status auf "{dict(self.fields["status"].choices)[status]}" gesetzt wird.' {
}) field_name: f'{field_label} ist erforderlich, wenn der Status auf "{dict(self.fields["status"].choices)[status]}" gesetzt wird.'
}
)
return cleaned_data return cleaned_data
@@ -184,8 +197,9 @@ class TicketForm(forms.ModelForm):
# Setze answered_at wenn eine Antwort gegeben wird # Setze answered_at wenn eine Antwort gegeben wird
if ticket.answer and not ticket.answered_at: if ticket.answer and not ticket.answered_at:
from django.utils import timezone from django.utils import timezone
ticket.answered_at = timezone.now() ticket.answered_at = timezone.now()
if commit: if commit:
ticket.save() ticket.save()
return ticket return ticket

View File

@@ -4,8 +4,11 @@ from django.contrib.auth.models import User
class Course(models.Model): class Course(models.Model):
"""Kurs-Model für Backend-Verwaltung""" """Kurs-Model für Backend-Verwaltung"""
name = models.CharField(max_length=200, verbose_name="Kurs-Name") name = models.CharField(max_length=200, verbose_name="Kurs-Name")
code = models.CharField(max_length=50, unique=True, verbose_name="Kurs-Code") # z.B. "PROG-101" code = models.CharField(
max_length=50, unique=True, verbose_name="Kurs-Code"
) # z.B. "PROG-101"
description = models.TextField(blank=True, verbose_name="Beschreibung") description = models.TextField(blank=True, verbose_name="Beschreibung")
tutor = models.ForeignKey( tutor = models.ForeignKey(
@@ -14,14 +17,14 @@ class Course(models.Model):
null=True, null=True,
blank=True, blank=True,
verbose_name="Tutor", verbose_name="Tutor",
related_name="courses_as_tutor" related_name="courses_as_tutor",
) )
is_active = models.BooleanField(default=True, verbose_name="Aktiv") is_active = models.BooleanField(default=True, verbose_name="Aktiv")
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
class Meta: class Meta:
ordering = ['name'] ordering = ["name"]
verbose_name = "Kurs" verbose_name = "Kurs"
verbose_name_plural = "Kurse" verbose_name_plural = "Kurse"
@@ -64,37 +67,40 @@ class Ticket(models.Model):
description = models.TextField() description = models.TextField()
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="new") status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="new")
mistake = models.CharField(max_length=20, choices=MISTAKE_CHOICES, default="medium") mistake = models.CharField(max_length=20, choices=MISTAKE_CHOICES, default="medium")
material = models.CharField(max_length=20, choices=MATERIAL_CHOICES, default="script") material = models.CharField(
max_length=20, choices=MATERIAL_CHOICES, default="script"
)
answer = models.TextField( answer = models.TextField(
blank=True, blank=True,
null=True, null=True,
verbose_name="Antwort/Lösung", verbose_name="Antwort/Lösung",
help_text="Beschreibung der Lösung (erforderlich bei Status 'Gelöst')" help_text="Beschreibung der Lösung (erforderlich bei Status 'Gelöst')",
) )
answered_at = models.DateTimeField( answered_at = models.DateTimeField(
blank=True, blank=True, null=True, verbose_name="Beantwortet am"
null=True,
verbose_name="Beantwortet am"
) )
course = models.ForeignKey( course = models.ForeignKey(
Course, Course, on_delete=models.CASCADE, verbose_name="Kurs", related_name="tickets"
on_delete=models.CASCADE,
verbose_name="Kurs",
related_name="tickets"
) )
created_by = models.ForeignKey( created_by = models.ForeignKey(
User, related_name="tickets_created", on_delete=models.CASCADE User, related_name="tickets_created", on_delete=models.CASCADE
) )
assigned_to = models.ForeignKey( assigned_to = models.ForeignKey(
User, related_name="tickets_assigned", null=True, blank=True, on_delete=models.CASCADE, User,
related_name="tickets_assigned",
null=True,
blank=True,
on_delete=models.CASCADE,
) )
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
def __str__(self): def __str__(self):
return f"[{self.get_mistake_display()}] {self.title} ({self.get_status_display()})" return (
f"[{self.get_mistake_display()}] {self.title} ({self.get_status_display()})"
)
class Comment(models.Model): class Comment(models.Model):
@@ -126,15 +132,16 @@ class TicketHistory(models.Model):
class FAQ(models.Model): class FAQ(models.Model):
"""Einfaches FAQ Model""" """Einfaches FAQ Model"""
question = models.CharField(max_length=300, verbose_name="Frage") question = models.CharField(max_length=300, verbose_name="Frage")
answer = models.TextField(verbose_name="Antwort") answer = models.TextField(verbose_name="Antwort")
order = models.IntegerField(default=0, verbose_name="Reihenfolge") order = models.IntegerField(default=0, verbose_name="Reihenfolge")
is_active = models.BooleanField(default=True, verbose_name="Aktiv") is_active = models.BooleanField(default=True, verbose_name="Aktiv")
class Meta: class Meta:
ordering = ['order', 'question'] ordering = ["order", "question"]
verbose_name = "FAQ" verbose_name = "FAQ"
verbose_name_plural = "FAQs" verbose_name_plural = "FAQs"
def __str__(self): def __str__(self):
return self.question return self.question

View File

@@ -8,7 +8,7 @@ from .views import (
AssignedTicketListView, AssignedTicketListView,
TicketDetailUpdateView, TicketDetailUpdateView,
faq_list, faq_list,
faq_pdf_download faq_pdf_download,
) )
urlpatterns = [ urlpatterns = [
@@ -22,6 +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/", faq_list, name="faq-list"),
path('faq/download/', faq_pdf_download, name='faq-pdf-download'), path("faq/download/", faq_pdf_download, name="faq-pdf-download"),
] ]