feat: added permissions and allowed transitions logic
This commit is contained in:
@@ -25,19 +25,104 @@ class TicketForm(forms.ModelForm):
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# User und ticket aus kwargs holen (werden von der View übergeben)
|
||||||
|
self.user = kwargs.pop('user', None)
|
||||||
|
self.ticket = kwargs.pop('ticket', None)
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Status-Choices basierend auf aktueller Situation einschränken
|
||||||
|
if self.ticket and self.user:
|
||||||
|
self._limit_status_choices()
|
||||||
|
self._set_field_permissions()
|
||||||
|
|
||||||
|
def _set_field_permissions(self):
|
||||||
|
"""Setzt welche Felder bearbeitet werden dürfen"""
|
||||||
|
is_creator = self.user == self.ticket.created_by
|
||||||
|
is_tutor = self.user == self.ticket.assigned_to
|
||||||
|
is_superuser = self.user.is_superuser
|
||||||
|
|
||||||
|
if is_tutor and not is_superuser:
|
||||||
|
# Tutor darf nur Status und Answer ändern
|
||||||
|
readonly_fields = ['title', 'description', 'course', 'priority']
|
||||||
|
for field_name in readonly_fields:
|
||||||
|
if field_name in self.fields:
|
||||||
|
self.fields[field_name].disabled = True
|
||||||
|
elif is_creator and not is_superuser:
|
||||||
|
# Ersteller darf gar nichts ändern
|
||||||
|
for field_name in self.fields:
|
||||||
|
self.fields[field_name].disabled = True
|
||||||
|
|
||||||
|
def _limit_status_choices(self):
|
||||||
|
"""Beschränkt die verfügbaren Status-Optionen basierend auf Rolle und aktuellem Status"""
|
||||||
|
current_status = self.ticket.status
|
||||||
|
is_creator = self.user == self.ticket.created_by
|
||||||
|
is_tutor = self.user == self.ticket.assigned_to
|
||||||
|
|
||||||
|
# Alle möglichen Status
|
||||||
|
all_choices = list(self.fields['status'].choices)
|
||||||
|
allowed_choices = []
|
||||||
|
|
||||||
|
if is_tutor:
|
||||||
|
if current_status == 'new':
|
||||||
|
allowed_choices = ['new', 'in_progress']
|
||||||
|
elif current_status == 'in_progress':
|
||||||
|
allowed_choices = ['in_progress', 'resolved', 'new']
|
||||||
|
elif current_status == 'resolved':
|
||||||
|
allowed_choices = ['resolved', 'closed']
|
||||||
|
elif current_status == 'closed':
|
||||||
|
allowed_choices = ['closed']
|
||||||
|
else:
|
||||||
|
# Nicht-Tutoren sehen nur aktuellen Status
|
||||||
|
allowed_choices = [current_status]
|
||||||
|
|
||||||
|
# Filtern der erlaubten Choices
|
||||||
|
self.fields['status'].choices = [
|
||||||
|
choice for choice in all_choices
|
||||||
|
if choice[0] in allowed_choices
|
||||||
|
]
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
status = cleaned_data.get('status')
|
status = cleaned_data.get('status')
|
||||||
answer = cleaned_data.get('answer')
|
answer = cleaned_data.get('answer')
|
||||||
|
|
||||||
# Wenn Status auf "gelöst" gesetzt wird, muss eine Antwort vorhanden sein
|
# Wenn Status auf "resolved" gesetzt wird, muss eine Antwort vorhanden sein
|
||||||
if status == 'resolved' and not answer:
|
if status == 'resolved' and not answer:
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'answer': 'Eine Antwort ist erforderlich, wenn der Status auf "Gelöst" gesetzt wird.'
|
'answer': 'Eine Antwort ist erforderlich, wenn der Status auf "Gelöst" gesetzt wird.'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
# Zusätzliche Validierung: Status-Übergang erlaubt?
|
||||||
|
if self.ticket and status:
|
||||||
|
old_status = self.ticket.status
|
||||||
|
if not self._is_status_transition_allowed(old_status, status):
|
||||||
|
raise ValidationError({
|
||||||
|
'status': f'Übergang von "{self.ticket.get_status_display()}" zu "{dict(self.fields["status"].choices)[status]}" ist nicht erlaubt.'
|
||||||
|
})
|
||||||
|
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
def _is_status_transition_allowed(self, old_status, new_status):
|
||||||
|
"""Prüft ob ein Status-Übergang erlaubt ist"""
|
||||||
|
if old_status == new_status:
|
||||||
|
return True
|
||||||
|
|
||||||
|
is_tutor = self.user == self.ticket.assigned_to
|
||||||
|
|
||||||
|
# Erlaubte Übergänge für Tutoren
|
||||||
|
allowed_transitions = {
|
||||||
|
'new': ['in_progress'],
|
||||||
|
'in_progress': ['resolved', 'new'],
|
||||||
|
'resolved': ['closed'],
|
||||||
|
'closed': [] # Keine Änderung von closed
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_tutor and old_status in allowed_transitions:
|
||||||
|
return new_status in allowed_transitions[old_status]
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def save(self, commit=True):
|
def save(self, commit=True):
|
||||||
ticket = super().save(commit=False)
|
ticket = super().save(commit=False)
|
||||||
|
|
||||||
|
|||||||
@@ -25,15 +25,15 @@
|
|||||||
<input type="text"
|
<input type="text"
|
||||||
name="title"
|
name="title"
|
||||||
value="{{ ticket.title }}"
|
value="{{ ticket.title }}"
|
||||||
{% if not view.can_edit %}disabled{% endif %}
|
{% if not view.can_edit or form.title.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 %}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 or form.title.field.disabled %}bg-gray-100{% endif %}">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium mb-1">Beschreibung:</label>
|
<label class="block text-sm font-medium mb-1">Beschreibung:</label>
|
||||||
<textarea name="description"
|
<textarea name="description"
|
||||||
rows="4"
|
rows="4"
|
||||||
{% if not view.can_edit %}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 %}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>
|
<div>
|
||||||
<label class="block text-sm font-medium mb-1">Kurs:</label>
|
<label class="block text-sm font-medium mb-1">Kurs:</label>
|
||||||
@@ -74,7 +74,7 @@
|
|||||||
<!-- 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-300 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">{% if ticket.assigned_to %}{{ ticket.assigned_to.username }}{% else %}Niemand zugewiesen{% endif %}</span>
|
<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>
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
rows="4"
|
rows="4"
|
||||||
{% 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-300 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>
|
<div class="text-red-600 text-sm mt-1">{{ form.answer.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@
|
|||||||
<label class="block text-sm font-medium mb-1">Status:</label>
|
<label class="block text-sm font-medium mb-1">Status:</label>
|
||||||
<select name="status"
|
<select name="status"
|
||||||
disabled
|
disabled
|
||||||
class="w-full p-2 border border-gray-300 bg-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 bg-gray-100 rounded shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
||||||
<option value="new"
|
<option value="new"
|
||||||
{% if form.status.value == 'new' or not form.status.value %}selected{% endif %}>
|
{% if form.status.value == 'new' or not form.status.value %}selected{% endif %}>
|
||||||
Neu
|
Neu
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
<!-- 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-300 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>
|
||||||
|
|||||||
@@ -79,19 +79,34 @@ class TicketDetailUpdateView(UpdateView):
|
|||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
self.ticket = self.get_object()
|
self.ticket = self.get_object()
|
||||||
user = request.user
|
user = request.user
|
||||||
# Prüfen, ob User bearbeiten darf
|
|
||||||
self.can_edit = (user == self.ticket.assigned_to) or user.is_superuser
|
# Erweiterte Berechtigungslogik
|
||||||
|
is_creator = user == self.ticket.created_by
|
||||||
|
is_assigned_tutor = user == self.ticket.assigned_to
|
||||||
|
is_superuser = user.is_superuser
|
||||||
|
|
||||||
|
self.can_edit = is_assigned_tutor or is_superuser
|
||||||
|
|
||||||
|
# Zusätzliche Flags für Template
|
||||||
|
self.is_creator = is_creator
|
||||||
|
self.is_tutor = is_assigned_tutor
|
||||||
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_form(self, form_class=None):
|
def get_form_kwargs(self):
|
||||||
form = super().get_form(form_class)
|
"""Übergibt zusätzliche kwargs ans Form"""
|
||||||
if not self.can_edit:
|
kwargs = super().get_form_kwargs()
|
||||||
for field in form.fields:
|
kwargs['user'] = self.request.user
|
||||||
form.fields[field].disabled = True # Felder lesbar, aber nicht änderbar
|
kwargs['ticket'] = self.object
|
||||||
return form
|
return kwargs
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**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
|
||||||
|
|
||||||
# Kommentarformular hinzufügen
|
# Kommentarformular hinzufügen
|
||||||
if "comment_form" not in context:
|
if "comment_form" not in context:
|
||||||
context["comment_form"] = self.comment_form_class()
|
context["comment_form"] = self.comment_form_class()
|
||||||
|
|||||||
Reference in New Issue
Block a user