feat: auto select tutor as assignee

This commit is contained in:
2025-05-29 22:52:35 +02:00
parent 5688d96e4f
commit c77c966e0e
4 changed files with 143 additions and 34 deletions

View File

@@ -1,5 +1,5 @@
from django import forms from django import forms
from .models import Comment from .models import Comment, Ticket
class CommentForm(forms.ModelForm): class CommentForm(forms.ModelForm):
@@ -11,3 +11,20 @@ class CommentForm(forms.ModelForm):
attrs={"rows": 3, "placeholder": "Kommentar schreiben..."} attrs={"rows": 3, "placeholder": "Kommentar schreiben..."}
), ),
} }
class TicketForm(forms.ModelForm):
class Meta:
model = Ticket
fields = ["title", "description", "status", "priority", "course"]
def save(self, commit=True):
ticket = super().save(commit=False)
# Automatische Tutor-Zuweisung basierend auf dem ausgewählten Kurs
if ticket.course and ticket.course.tutor:
ticket.assigned_to = ticket.course.tutor
if commit:
ticket.save()
return ticket

View File

@@ -38,6 +38,7 @@
<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"
id="id_course"
{% if not view.can_edit %}disabled{% endif %} {% 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 %}"> 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 course in form.course.field.queryset %} {% for course in form.course.field.queryset %}
@@ -72,15 +73,10 @@
</div> </div>
<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>
<select name="assigned_to" <div id="tutor_display" class="w-full p-2 border border-gray-300 bg-gray-100 rounded shadow-sm text-gray-300">
{% if not view.can_edit %}disabled{% endif %} <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 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 %}"> </div>
<option value="">Nicht zugewiesen</option> <p class="text-xs text-gray-500 mt-1">Wird automatisch basierend auf dem ausgewählten Kurs zugewiesen</p>
{% for user in form.assigned_to.field.queryset %}
<option value="{{ user.pk }}"
{% if ticket.assigned_to == user %}selected{% endif %}>{{ user.username }}</option>
{% endfor %}
</select>
</div> </div>
{% if view.can_edit %} {% if view.can_edit %}
<button type="submit" <button type="submit"
@@ -148,4 +144,39 @@
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 -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const courseSelect = document.getElementById('id_course');
const tutorText = document.getElementById('tutor_text');
const tutorDisplay = document.getElementById('tutor_display');
// Course-Tutor Mapping
const courseTutorMap = {
{% for course in form.course.field.queryset %}
{% if course.tutor %}
'{{ course.pk }}': '{{ course.tutor.username }}',
{% endif %}
{% endfor %}
};
// Nur aktivieren wenn User bearbeiten kann
{% if view.can_edit %}
courseSelect.addEventListener('change', function() {
const selectedCourseId = this.value;
if (selectedCourseId && courseTutorMap[selectedCourseId]) {
tutorText.textContent = courseTutorMap[selectedCourseId];
tutorDisplay.classList.remove('bg-gray-100');
tutorDisplay.classList.add('bg-blue-50');
} else if (selectedCourseId) {
tutorText.textContent = 'Kein Tutor zugewiesen';
tutorDisplay.classList.remove('bg-blue-50');
tutorDisplay.classList.add('bg-gray-100');
}
});
{% endif %}
});
</script>
{% endblock %} {% endblock %}

View File

@@ -47,6 +47,7 @@
<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"
id="id_course"
required 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"> 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="">Kurs auswählen...</option> <option value="">Kurs auswählen...</option>
@@ -90,17 +91,10 @@
<!-- 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>
<select name="assigned_to" <div id="tutor_display" class="w-full p-2 border border-gray-300 bg-gray-300 rounded shadow-sm">
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"> <span id="tutor_text">Bitte Kurs auswählen</span>
<option value="">Nicht zugewiesen</option> </div>
{% for user in form.assigned_to.field.queryset %} <p class="text-xs text-gray-500 mt-1">Wird automatisch basierend auf dem ausgewählten Kurs zugewiesen</p>
<option value="{{ user.pk }}"
{% if form.assigned_to.value == user.pk %}selected{% endif %}>
{{ user.username }}
</option>
{% endfor %}
</select>
{% if form.assigned_to.errors %}<div class="text-red-600 text-sm mt-1">{{ form.assigned_to.errors }}</div>{% endif %}
</div> </div>
<!-- Buttons --> <!-- Buttons -->
<div class="flex gap-4 pt-4"> <div class="flex gap-4 pt-4">
@@ -122,8 +116,54 @@
<li> <li>
• Beschreibe das Problem so <strong>detailliert wie möglich</strong> • Beschreibe das Problem so <strong>detailliert wie möglich</strong>
</li> </li>
<li>
• Das Ticket wird automatisch dem <strong>Kurs-Tutor zugewiesen</strong>
</li>
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const courseSelect = document.getElementById('id_course');
const tutorText = document.getElementById('tutor_text');
const tutorDisplay = document.getElementById('tutor_display');
// Course-Tutor Mapping speichern
const courseTutorMap = {
{% for course in form.course.field.queryset %}
{% if course.tutor %}
'{{ course.pk }}': '{{ course.tutor.username }}',
{% endif %}
{% endfor %}
};
courseSelect.addEventListener('change', function() {
const selectedCourseId = this.value;
if (selectedCourseId && courseTutorMap[selectedCourseId]) {
// Tutor gefunden
tutorText.textContent = courseTutorMap[selectedCourseId];
tutorDisplay.classList.remove('bg-gray-100');
tutorDisplay.classList.add('bg-blue-50');
} else if (selectedCourseId) {
// Kurs hat keinen Tutor
tutorText.textContent = 'Kein Tutor zugewiesen';
tutorDisplay.classList.remove('bg-blue-50');
tutorDisplay.classList.add('bg-gray-100');
} else {
// Kein Kurs ausgewählt
tutorText.textContent = 'Bitte erst Kurs auswählen';
tutorDisplay.classList.remove('bg-blue-50');
tutorDisplay.classList.add('bg-gray-100');
}
});
// Initial load: Check if course is already selected
if (courseSelect.value) {
courseSelect.dispatchEvent(new Event('change'));
}
});
</script>
{% endblock %} {% endblock %}

View File

@@ -3,7 +3,7 @@ from django.views.generic.edit import CreateView, UpdateView
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views.generic.detail import DetailView from django.views.generic.detail import DetailView
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from .forms import CommentForm 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
@@ -60,9 +60,10 @@ class TicketListView(ListView):
context["status_choices"] = Ticket.STATUS_CHOICES context["status_choices"] = Ticket.STATUS_CHOICES
return context return context
class TicketDetailUpdateView(UpdateView): class TicketDetailUpdateView(UpdateView):
model = Ticket model = Ticket
fields = ["title", "description", "status", "priority", "assigned_to", "course"] form_class = TicketForm # Verwende das custom Form anstatt fields
template_name = "ticketsystem/detail.html" template_name = "ticketsystem/detail.html"
comment_form_class = CommentForm comment_form_class = CommentForm
@@ -93,25 +94,29 @@ class TicketDetailUpdateView(UpdateView):
def form_valid(self, form): def form_valid(self, form):
ticket = form.instance ticket = form.instance
original = Ticket.objects.get(pk=ticket.pk) 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 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", "assigned_to", "course"] tracked_fields = ["title", "description", "status", "priority", "course"]
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)
new_value = form.cleaned_data.get(field) new_value = form.cleaned_data.get(field)
# Für ForeignKey Felder den Display-Namen verwenden # Für ForeignKey Felder den Display-Namen verwenden
if field == "assigned_to": if field == "status":
old_value = old_value.username if old_value else "Niemand"
new_value = new_value.username if new_value else "Niemand"
elif field == "status":
old_value = original.get_status_display() old_value = original.get_status_display()
new_value = ticket.get_status_display() new_value = ticket.get_status_display()
elif field == "priority": elif field == "priority":
old_value = original.get_priority_display() old_value = original.get_priority_display()
new_value = ticket.get_priority_display() new_value = ticket.get_priority_display()
elif field == "course":
old_value = str(old_value)
new_value = str(new_value)
TicketHistory.objects.create( TicketHistory.objects.create(
ticket=ticket, ticket=ticket,
@@ -121,12 +126,28 @@ class TicketDetailUpdateView(UpdateView):
new_value=str(new_value), new_value=str(new_value),
) )
if form.changed_data: 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( messages.success(
self.request, self.request,
f"Ticket erfolgreich aktualisiert. Geänderte Felder: {', '.join(form.changed_data)}", f"Ticket erfolgreich aktualisiert. Geänderte Felder: {', '.join(changed_fields)}",
) )
return response return response
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@@ -168,7 +189,7 @@ class AssignedTicketListView(LoginRequiredMixin, ListView):
class TicketCreateView(CreateView): class TicketCreateView(CreateView):
model = Ticket model = Ticket
fields = ["title", "description", "status", "priority", "assigned_to", "course"] form_class = TicketForm
template_name = "ticketsystem/ticket_form.html" template_name = "ticketsystem/ticket_form.html"
def form_valid(self, form): def form_valid(self, form):