feat: added material to model and form
This commit is contained in:
@@ -17,7 +17,7 @@ class CommentForm(forms.ModelForm):
|
|||||||
class TicketForm(forms.ModelForm):
|
class TicketForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ticket
|
model = Ticket
|
||||||
fields = ["title", "description", "status", "priority", "course", "answer"]
|
fields = ["title", "description", "status", "priority", "course", "answer", "material"]
|
||||||
widgets = {
|
widgets = {
|
||||||
'answer': forms.Textarea(attrs={
|
'answer': forms.Textarea(attrs={
|
||||||
'rows': 4,
|
'rows': 4,
|
||||||
|
|||||||
@@ -46,10 +46,21 @@ class Ticket(models.Model):
|
|||||||
("high", "Hoch"),
|
("high", "Hoch"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
MATERIAL_CHOICES = [
|
||||||
|
("learning_sprint", "Learning Sprint"),
|
||||||
|
("ilse", "Intensive Live Session"),
|
||||||
|
("video", "Video"),
|
||||||
|
("exercise", "Übungsaufgabe"),
|
||||||
|
("solution", "Musterlösung"),
|
||||||
|
("slides", "Präsentationsfolien"),
|
||||||
|
("other", "Sonstiges"),
|
||||||
|
]
|
||||||
|
|
||||||
title = models.CharField(max_length=200)
|
title = models.CharField(max_length=200)
|
||||||
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")
|
||||||
priority = models.CharField(max_length=10, choices=PRIORITY_CHOICES, default="medium")
|
priority = models.CharField(max_length=10, choices=PRIORITY_CHOICES, default="medium")
|
||||||
|
material = models.CharField(max_length=20, choices=MATERIAL_CHOICES, default="script")
|
||||||
|
|
||||||
answer = models.TextField(
|
answer = models.TextField(
|
||||||
blank=True,
|
blank=True,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
{% if not view.can_edit or form.description.field.disabled %}disabled{% endif %}
|
{% if not view.can_edit or form.description.field.disabled %}disabled{% endif %}
|
||||||
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 {% if not view.can_edit or form.description.field.disabled %}bg-gray-100{% endif %}">{{ ticket.description }}</textarea>
|
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 {% if not view.can_edit or form.description.field.disabled %}bg-gray-100{% endif %}">{{ ticket.description }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium mb-1">Kurs:</label>
|
<label class="block text-sm font-medium mb-1">Kurs:</label>
|
||||||
<select name="course"
|
<select name="course"
|
||||||
@@ -47,6 +48,19 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Material:</label>
|
||||||
|
<select name="material"
|
||||||
|
id="id_material"
|
||||||
|
{% if not view.can_edit %}disabled{% endif %}
|
||||||
|
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 {% if not view.can_edit %}bg-gray-100{% endif %}">
|
||||||
|
{% for value, display in form.fields.material.choices %}
|
||||||
|
<option value="{{ value }}"
|
||||||
|
{% if form.material.value == value %}selected{% endif %}>{{ display }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium mb-1">Status:</label>
|
<label class="block text-sm font-medium mb-1">Status:</label>
|
||||||
@@ -74,28 +88,30 @@
|
|||||||
<!-- Zugewiesen an (Nur Anzeige) -->
|
<!-- Zugewiesen an (Nur Anzeige) -->
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium mb-1">Zugewiesen an:</label>
|
<label class="block text-sm font-medium mb-1">Zugewiesen an:</label>
|
||||||
<div id="tutor_display" class="w-full p-2 border border-gray-300 bg-gray-100 rounded shadow-sm">
|
<div id="tutor_display"
|
||||||
<span id="tutor_text">{% if ticket.assigned_to %}{{ ticket.assigned_to.username }}{% else %}Niemand zugewiesen{% endif %}</span>
|
class="w-full p-2 border border-gray-300 bg-gray-100 rounded shadow-sm">
|
||||||
|
<span id="tutor_text">
|
||||||
|
{% if ticket.assigned_to %}
|
||||||
|
{{ ticket.assigned_to.username }}
|
||||||
|
{% else %}
|
||||||
|
Niemand zugewiesen
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-500 mt-1">Wird automatisch basierend auf dem ausgewählten Kurs zugewiesen</p>
|
<p class="text-xs text-gray-500 mt-1">Wird automatisch basierend auf dem ausgewählten Kurs zugewiesen</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Antwort/Lösung -->
|
<!-- Antwort/Lösung -->
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium mb-1">
|
<label class="block text-sm font-medium mb-1">
|
||||||
Antwort/Lösung:
|
Antwort/Lösung:
|
||||||
{% if form.status.value == 'resolved' or ticket.status == 'resolved' %}
|
{% if form.status.value == 'resolved' or ticket.status == 'resolved' %}<span class="text-red-500">*</span>{% endif %}
|
||||||
<span class="text-red-500">*</span>
|
|
||||||
{% endif %}
|
|
||||||
</label>
|
</label>
|
||||||
{% if ticket.answer and not view.can_edit %}
|
{% if ticket.answer and not view.can_edit %}
|
||||||
<!-- Nur Anzeige wenn bereits beantwortet und nicht editierbar -->
|
<!-- Nur Anzeige wenn bereits beantwortet und nicht editierbar -->
|
||||||
<div class="w-full p-3 border border-gray-300 bg-gray-50 rounded shadow-sm">
|
<div class="w-full p-3 border border-gray-300 bg-gray-50 rounded shadow-sm">
|
||||||
<div class="text-gray-700">{{ ticket.answer|linebreaks }}</div>
|
<div class="text-gray-700">{{ ticket.answer|linebreaks }}</div>
|
||||||
{% if ticket.answered_at %}
|
{% if ticket.answered_at %}
|
||||||
<div class="text-xs text-gray-500 mt-2">
|
<div class="text-xs text-gray-500 mt-2">Beantwortet am: {{ ticket.answered_at|date:"d.m.Y H:i" }}</div>
|
||||||
Beantwortet am: {{ ticket.answered_at|date:"d.m.Y H:i" }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -105,13 +121,9 @@
|
|||||||
{% if not view.can_edit or ticket.status != 'resolved' %}disabled{% endif %}
|
{% if not view.can_edit or ticket.status != 'resolved' %}disabled{% endif %}
|
||||||
placeholder="Beschreibe die Lösung des Problems..."
|
placeholder="Beschreibe die Lösung des Problems..."
|
||||||
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 placeholder:text-black {% if not view.can_edit or ticket.status != 'resolved' %}bg-gray-100 cursor-not-allowed{% endif %}">{{ ticket.answer|default:'' }}</textarea>
|
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 placeholder:text-black {% if not view.can_edit or ticket.status != 'resolved' %}bg-gray-100 cursor-not-allowed{% endif %}">{{ ticket.answer|default:'' }}</textarea>
|
||||||
{% if form.answer.errors %}
|
{% if form.answer.errors %}<div class="text-red-600 text-sm mt-1">{{ form.answer.errors }}</div>{% endif %}
|
||||||
<div class="text-red-600 text-sm mt-1">{{ form.answer.errors }}</div>
|
|
||||||
{% endif %}
|
|
||||||
<p class="text-xs text-gray-500 mt-1">
|
<p class="text-xs text-gray-500 mt-1">
|
||||||
{% if ticket.status != 'resolved' %}
|
{% if ticket.status != 'resolved' %}Eine Antwort ist erforderlich beim Setzen des Status auf "Gelöst"{% endif %}
|
||||||
Eine Antwort ist erforderlich beim Setzen des Status auf "Gelöst"
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -181,7 +193,6 @@
|
|||||||
class="text-blue-500 hover:text-blue-600">← Zurück zur Übersicht</a>
|
class="text-blue-500 hover:text-blue-600">← Zurück zur Übersicht</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- JavaScript für dynamische Tutor-Anzeige -->
|
<!-- JavaScript für dynamische Tutor-Anzeige -->
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
@@ -192,9 +203,7 @@
|
|||||||
// Course-Tutor Mapping
|
// Course-Tutor Mapping
|
||||||
const courseTutorMap = {
|
const courseTutorMap = {
|
||||||
{% for course in form.course.field.queryset %}
|
{% for course in form.course.field.queryset %}
|
||||||
{% if course.tutor %}
|
{% if course.tutor %}'{{ course.pk }}': '{{ course.tutor.username }}',{% endif %}
|
||||||
'{{ course.pk }}': '{{ course.tutor.username }}',
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
<div class="text-red-600 text-sm mt-1">{{ form.description.errors }}</div>
|
<div class="text-red-600 text-sm mt-1">{{ form.description.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Kurs und Material -->
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium mb-1">Kurs: *</label>
|
<label class="block text-sm font-medium mb-1">Kurs: *</label>
|
||||||
<select name="course"
|
<select name="course"
|
||||||
@@ -53,11 +55,28 @@
|
|||||||
<option value="">Kurs auswählen...</option>
|
<option value="">Kurs auswählen...</option>
|
||||||
{% for course in form.course.field.queryset %}
|
{% for course in form.course.field.queryset %}
|
||||||
<option value="{{ course.pk }}"
|
<option value="{{ course.pk }}"
|
||||||
{% if form.course.value == course.pk %}selected{% endif %}>{{ course }}</option>
|
{% if form.course.value == course.pk %}selected{% endif %}>
|
||||||
|
{{ course }}
|
||||||
|
</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
{% if form.course.errors %}<div class="text-red-600 text-sm mt-1">{{ form.course.errors }}</div>{% endif %}
|
{% if form.course.errors %}<div class="text-red-600 text-sm mt-1">{{ form.course.errors }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Material: *</label>
|
||||||
|
<select name="material"
|
||||||
|
id="id_material"
|
||||||
|
required
|
||||||
|
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<option value="">Material auswählen...</option>
|
||||||
|
{% for value, display in form.fields.material.choices %}
|
||||||
|
<option value="{{ value }}"
|
||||||
|
{% if form.material.value == value %}selected{% endif %}>{{ display }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% if form.course.errors %}<div class="text-red-600 text-sm mt-1">{{ form.course.errors }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Status und Priorität -->
|
<!-- Status und Priorität -->
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
@@ -77,13 +96,10 @@
|
|||||||
<label class="block text-sm font-medium mb-1">Priorität:</label>
|
<label class="block text-sm font-medium mb-1">Priorität:</label>
|
||||||
<select name="priority"
|
<select name="priority"
|
||||||
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
class="w-full p-2 border border-gray-300 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
<option value="low" {% if form.priority.value == 'low' %}selected{% endif %}>Niedrig</option>
|
{% for value, display in form.fields.priority.choices %}
|
||||||
<option value="medium"
|
<option value="{{ value }}"
|
||||||
{% if form.priority.value == 'medium' or not form.priority.value %}selected{% endif %}>
|
{% if form.priority.value == value %}selected{% endif %}>{{ display }}</option>
|
||||||
Normal
|
{% endfor %}
|
||||||
</option>
|
|
||||||
<option value="high"
|
|
||||||
{% if form.priority.value == 'high' %}selected{% endif %}>Hoch</option>
|
|
||||||
</select>
|
</select>
|
||||||
{% if form.priority.errors %}<div class="text-red-600 text-sm mt-1">{{ form.priority.errors }}</div>{% endif %}
|
{% if form.priority.errors %}<div class="text-red-600 text-sm mt-1">{{ form.priority.errors }}</div>{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -91,7 +107,8 @@
|
|||||||
<!-- Zugewiesen an -->
|
<!-- Zugewiesen an -->
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium mb-1">Zugewiesen an:</label>
|
<label class="block text-sm font-medium mb-1">Zugewiesen an:</label>
|
||||||
<div id="tutor_display" class="w-full p-2 border border-gray-300 bg-gray-100 rounded shadow-sm">
|
<div id="tutor_display"
|
||||||
|
class="w-full p-2 border border-gray-300 bg-gray-100 rounded shadow-sm">
|
||||||
<span id="tutor_text">Bitte Kurs auswählen</span>
|
<span id="tutor_text">Bitte Kurs auswählen</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-gray-500 mt-1">Wird automatisch basierend auf dem ausgewählten Kurs zugewiesen</p>
|
<p class="text-xs text-gray-500 mt-1">Wird automatisch basierend auf dem ausgewählten Kurs zugewiesen</p>
|
||||||
@@ -123,7 +140,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const courseSelect = document.getElementById('id_course');
|
const courseSelect = document.getElementById('id_course');
|
||||||
@@ -133,9 +149,7 @@
|
|||||||
// Course-Tutor Mapping speichern
|
// Course-Tutor Mapping speichern
|
||||||
const courseTutorMap = {
|
const courseTutorMap = {
|
||||||
{% for course in form.course.field.queryset %}
|
{% for course in form.course.field.queryset %}
|
||||||
{% if course.tutor %}
|
{% if course.tutor %}'{{ course.pk }}': '{{ course.tutor.username }}',{% endif %}
|
||||||
'{{ course.pk }}': '{{ course.tutor.username }}',
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -85,6 +85,10 @@ class TicketDetailUpdateView(UpdateView):
|
|||||||
is_assigned_tutor = user == self.ticket.assigned_to
|
is_assigned_tutor = user == self.ticket.assigned_to
|
||||||
is_superuser = user.is_superuser
|
is_superuser = user.is_superuser
|
||||||
|
|
||||||
|
# Nur Autor und Admin kann Tickets bearbeiten
|
||||||
|
if self.ticket.status == "closed" and not is_superuser:
|
||||||
|
self.can_edit = False
|
||||||
|
else:
|
||||||
self.can_edit = is_assigned_tutor or is_superuser
|
self.can_edit = is_assigned_tutor or is_superuser
|
||||||
|
|
||||||
# Zusätzliche Flags für Template
|
# Zusätzliche Flags für Template
|
||||||
@@ -122,7 +126,7 @@ class TicketDetailUpdateView(UpdateView):
|
|||||||
response = super().form_valid(form) # Speichert das Ticket
|
response = super().form_valid(form) # Speichert das Ticket
|
||||||
|
|
||||||
# History tracking für geänderte Felder
|
# History tracking für geänderte Felder
|
||||||
tracked_fields = ["title", "description", "status", "priority", "course", "answer"]
|
tracked_fields = ["title", "description", "status", "priority", "course", "answer", "material"]
|
||||||
for field in tracked_fields:
|
for field in tracked_fields:
|
||||||
if field in form.changed_data:
|
if field in form.changed_data:
|
||||||
old_value = getattr(original, field)
|
old_value = getattr(original, field)
|
||||||
|
|||||||
Reference in New Issue
Block a user