feat: replaced TicketDetailView with TicketDetailUpdateView

This commit is contained in:
2025-05-26 20:16:10 +02:00
parent 6306ffd432
commit 8cc038ee20
3 changed files with 319 additions and 108 deletions

View File

@@ -1,7 +1,7 @@
{% extends "ticketsystem/base.html" %} {% extends "ticketsystem/base.html" %}
{% block content %} {% block content %}
{% if messages %} {% if messages %}
<div style="max-width: 600px; margin: 1rem auto;"> <div style="max-width: 700px; margin: 1rem auto;">
{% for message in messages %} {% for message in messages %}
<div style="padding: 1rem; border-radius: 5px; margin-bottom: 1rem; <div style="padding: 1rem; border-radius: 5px; margin-bottom: 1rem;
background-color: {% if message.tags == 'error' %}#f8d7da background-color: {% if message.tags == 'error' %}#f8d7da
@@ -13,6 +13,7 @@
{% endfor %} {% endfor %}
</div> </div>
{% endif %} {% endif %}
<style> <style>
.ticket-container { .ticket-container {
max-width: 700px; max-width: 700px;
@@ -24,24 +25,48 @@
font-family: sans-serif; font-family: sans-serif;
} }
.ticket-container h1 { .form-group {
margin-bottom: 1rem; margin-bottom: 1rem;
font-size: 1.8rem; }
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 0.3rem;
color: #333; color: #333;
} }
.ticket-attribute { .form-group input, .form-group select, .form-group textarea {
margin-bottom: 1rem; width: 100%;
padding: 0.5rem;
border: 1px solid #ccc;
border-radius: 4px;
font-family: inherit;
} }
.ticket-attribute strong { .form-group textarea {
display: inline-block; min-height: 100px;
width: 150px; resize: vertical;
color: #555; }
.btn {
background: #007bff;
color: white;
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
}
.btn:hover {
background: #0056b3;
} }
.ticket-meta { .ticket-meta {
margin-top: 2rem; margin-top: 2rem;
padding-top: 1rem;
border-top: 1px solid #ddd;
font-size: 0.9rem; font-size: 0.9rem;
color: #666; color: #666;
} }
@@ -54,57 +79,162 @@
border-radius: 4px; border-radius: 4px;
} }
.comment-meta {
font-size: 0.9rem;
color: #666;
margin-bottom: 0.5rem;
}
.comment-text {
color: #333;
line-height: 1.4;
}
.comment-form {
margin-top: 1.5rem;
padding: 1.5rem;
background: #f8f9fa;
border-radius: 6px;
border: 1px solid #e9ecef;
}
.comment-form h3 {
margin-top: 0;
margin-bottom: 1rem;
color: #333;
}
.comment-form textarea { .comment-form textarea {
width: 100%; width: 100%;
padding: 0.5rem; padding: 0.75rem;
border-radius: 4px; border-radius: 4px;
border: 1px solid #ccc; border: 1px solid #ccc;
min-height: 80px;
resize: vertical;
} }
.comment-form button { .comment-form button {
margin-top: 0.5rem; margin-top: 0.5rem;
background: #007bff; background: #28a745;
color: white; color: white;
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
border: none; border: none;
border-radius: 4px; border-radius: 4px;
cursor: pointer;
} }
.comment-form button:hover {
background: #218838;
}
.history-entry {
margin-bottom: 1rem;
background-color: #f9f9f9;
border-left: 4px solid #6c757d;
padding: 0.75rem 1rem;
border-radius: 4px;
}
.history-user {
font-weight: bold;
color: #333;
}
.history-field {
background: #e9ecef;
padding: 0.2rem 0.4rem;
border-radius: 3px;
font-family: monospace;
font-size: 0.9rem;
}
.history-change {
margin: 0.5rem 0;
}
.history-old {
color: #dc3545;
font-style: italic;
}
.history-new {
color: #28a745;
font-weight: bold;
}
.history-date {
font-size: 0.8rem;
color: #666;
}
.section-container {
max-width: 700px;
margin: 1rem auto;
padding: 2rem;
border: 1px solid #ddd;
border-radius: 8px;
background-color: #fafafa;
font-family: sans-serif;
}
.section-container h2 {
margin-top: 0;
margin-bottom: 1.5rem;
color: #333;
border-bottom: 2px solid #007bff;
padding-bottom: 0.5rem;
}
.no-content {
color: #666;
font-style: italic;
text-align: center;
padding: 2rem;
background: #f8f9fa;
border-radius: 4px;
border: 1px dashed #dee2e6;
}
</style> </style>
<!-- Ticket Bearbeitung -->
<div class="ticket-container"> <div class="ticket-container">
<h1>🎫 Ticket #{{ ticket.id }} {{ ticket.title }}</h1> <h1>🎫 Ticket #{{ ticket.id }} {{ ticket.title }}</h1>
<p style="text-align: right;"> <form method="post" style="margin-top: 1rem;">
<a href="{% url 'modify' ticket.pk %}" style="text-decoration: none; font-weight: bold;">✏️ Dieses Ticket {% csrf_token %}
bearbeiten</a> {{ form.non_field_errors }}
</p>
<div class="ticket-attribute"> <div class="form-group">
<strong>Status:</strong> {{ ticket.get_status_display }} <label for="id_title">Titel:</label>
{{ form.title }}
</div> </div>
<div class="ticket-attribute"> <div class="form-group">
<strong>Priorität:</strong> {{ ticket.get_priority_display }} <label for="id_description">Beschreibung:</label>
{{ form.description }}
</div> </div>
<div class="ticket-attribute"> <div class="form-group">
<strong>Beschreibung:</strong><br> <label for="id_status">Status:</label>
<div style="margin-top: 0.5rem;">{{ ticket.description }}</div> {{ form.status }}
</div> </div>
<div class="ticket-attribute"> <div class="form-group">
<strong>Erstellt von:</strong> {{ ticket.created_by.username }} <label for="id_priority">Priorität:</label>
{{ form.priority }}
</div> </div>
<div class="ticket-attribute"> <div class="form-group">
<strong>Zugewiesen an:</strong> <label for="id_assigned_to">Zugewiesen an:</label>
{% if ticket.assigned_to %} {{ form.assigned_to }}
{{ ticket.assigned_to.username }} </div>
{% if view.can_edit %}
<button type="submit" class="btn">💾 Änderungen speichern</button>
{% else %} {% else %}
<em>Niemand zugewiesen</em> <p style="color: #999; margin-top: 1rem;">Du darfst dieses Ticket nicht bearbeiten.</p>
{% endif %} {% endif %}
</div> </form>
<div class="ticket-meta"> <div class="ticket-meta">
🕒 Erstellt am: {{ ticket.created_at|date:"d.m.Y H:i" }}<br> 🕒 Erstellt am: {{ ticket.created_at|date:"d.m.Y H:i" }}<br>
@@ -112,43 +242,59 @@
</div> </div>
</div> </div>
<div class="ticket-container" style="margin-top: 2rem;"> <!-- Kommentare Sektion -->
<div class="section-container">
<h2>💬 Kommentare</h2> <h2>💬 Kommentare</h2>
{% if ticket.comments.exists %} {% if ticket.comments.exists %}
{% for comment in ticket.comments.all %} {% for comment in ticket.comments.all %}
<div class="comment"> <div class="comment">
<p><strong>{{ comment.author.username }}</strong> am {{ comment.created_at|date:"d.m.Y H:i" }}</p> <div class="comment-meta">
<p>{{ comment.text }}</p> <strong>{{ comment.author.username }}</strong> am {{ comment.created_at|date:"d.m.Y H:i" }}
</div>
<div class="comment-text">{{ comment.text }}</div>
</div> </div>
{% endfor %} {% endfor %}
{% else %} {% else %}
<p>Keine Kommentare vorhanden.</p> <div class="no-content">
Keine Kommentare vorhanden.
</div>
{% endif %} {% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<div class="comment-form">
<h3>📝 Neuen Kommentar schreiben</h3> <h3>📝 Neuen Kommentar schreiben</h3>
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
{{ form.as_p }} {{ comment_form.as_p }}
<button type="submit">Absenden</button> <button type="submit" name="comment_submit">Kommentar absenden</button>
</form> </form>
</div>
{% endif %} {% endif %}
<div style="margin-top: 3rem;"> </div>
<!-- Bearbeitungshistorie Sektion -->
<div class="section-container">
<h2>🕓 Bearbeitungshistorie</h2> <h2>🕓 Bearbeitungshistorie</h2>
{% if ticket.history.exists %} {% if ticket.history.exists %}
<ul style="list-style: none; padding: 0;">
{% for entry in ticket.history.all %} {% for entry in ticket.history.all %}
<li style="margin-bottom: 1rem; background-color: #f9f9f9; border-left: 4px solid #ccc; padding: 0.5rem 1rem;"> <div class="history-entry">
<strong>{{ entry.changed_by.username }}</strong> <div class="history-user">{{ entry.changed_by.username }}</div>
hat <code>{{ entry.field }}</code> geändert:<br> <div class="history-change">
<em>{{ entry.old_value }}</em><strong>{{ entry.new_value }}</strong><br> hat <span class="history-field">{{ entry.field }}</span> geändert:
<small>am {{ entry.changed_at|date:"d.m.Y H:i" }}</small> </div>
</li> <div class="history-change">
<span class="history-old">{{ entry.old_value }}</span><span class="history-new">{{ entry.new_value }}</span>
</div>
<div class="history-date">am {{ entry.changed_at|date:"d.m.Y H:i" }}</div>
</div>
{% endfor %} {% endfor %}
</ul>
{% else %} {% else %}
<p><em>Keine Änderungen bisher.</em></p> <div class="no-content">
Keine Änderungen bisher.
</div>
{% endif %} {% endif %}
</div> </div>
</div>
{% endblock %} {% endblock %}

View File

@@ -6,8 +6,8 @@ from .views import (
TicketCreateView, TicketCreateView,
TicketUpdateView, TicketUpdateView,
HomeView, HomeView,
TicketDetailView, AssignedTicketListView,
AssignedTicketListView TicketDetailUpdateView
) )
urlpatterns = [ urlpatterns = [
@@ -16,7 +16,7 @@ urlpatterns = [
# /ticketsystem/tickets # /ticketsystem/tickets
path("tickets", TicketListView.as_view(), name="ticket-list"), path("tickets", TicketListView.as_view(), name="ticket-list"),
# /ticketsystem/detail/ # /ticketsystem/detail/
path("<int:pk>/", TicketDetailView.as_view(), name="detail"), path('<int:pk>/', TicketDetailUpdateView.as_view(), name='detail'),
# /ticketsystem/new/ # /ticketsystem/new/
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"),

View File

@@ -46,31 +46,96 @@ class TicketListView(ListView):
return context return context
class TicketDetailView(FormMixin, DetailView): class TicketDetailUpdateView(UpdateView):
model = Ticket model = Ticket
fields = ["title", "description", "status", "priority", "assigned_to"]
template_name = "ticketsystem/detail.html" template_name = "ticketsystem/detail.html"
context_object_name = "ticket" comment_form_class = CommentForm
form_class = CommentForm
def get_success_url(self): def get_success_url(self):
return reverse("detail", kwargs={"pk": self.object.pk}) return reverse('detail-update', kwargs={'pk': self.object.pk})
def post(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.object = self.get_object() self.ticket = self.get_object()
form = self.get_form() user = request.user
if form.is_valid(): # Prüfen, ob User bearbeiten darf
comment = form.save(commit=False) self.can_edit = (user == self.ticket.assigned_to) or user.is_superuser
comment.ticket = self.object return super().dispatch(request, *args, **kwargs)
comment.author = request.user
comment.save() def get_form(self, form_class=None):
return super().form_valid(form) form = super().get_form(form_class)
return self.form_invalid(form) if not self.can_edit:
for field in form.fields:
form.fields[field].disabled = True # Felder lesbar, aber nicht änderbar
return form
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["form"] = self.get_form() # Kommentarformular hinzufügen
if 'comment_form' not in context:
context['comment_form'] = self.comment_form_class()
return context return context
def form_valid(self, form):
ticket = form.instance
original = Ticket.objects.get(pk=ticket.pk)
response = super().form_valid(form) # Speichert das Ticket
# History tracking für geänderte Felder
tracked_fields = ["title", "description", "status", "priority", "assigned_to"]
for field in tracked_fields:
if field in form.changed_data:
old_value = getattr(original, field)
new_value = form.cleaned_data.get(field)
# Für ForeignKey Felder den Display-Namen verwenden
if field == "assigned_to":
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()
new_value = ticket.get_status_display()
elif field == "priority":
old_value = original.get_priority_display()
new_value = ticket.get_priority_display()
TicketHistory.objects.create(
ticket=ticket,
changed_by=self.request.user,
field=field,
old_value=str(old_value),
new_value=str(new_value),
)
if form.changed_data:
messages.success(self.request,
f"Ticket erfolgreich aktualisiert. Geänderte Felder: {', '.join(form.changed_data)}")
return response
def post(self, request, *args, **kwargs):
self.object = self.get_object() # Wichtig: object setzen für beide Fälle
if 'comment_submit' in request.POST:
# Kommentar absenden
comment_form = self.comment_form_class(request.POST)
if comment_form.is_valid():
comment = comment_form.save(commit=False)
comment.ticket = self.object
comment.author = request.user
comment.save()
messages.success(request, "Kommentar hinzugefügt.")
return redirect(self.get_success_url())
else:
# Kommentarformular fehlerhaft: Seite neu laden mit Fehlern
context = self.get_context_data(comment_form=comment_form)
return self.render_to_response(context)
else:
# Ticket bearbeiten (UpdateView standard)
if not self.can_edit:
messages.error(request, "⛔ Du darfst dieses Ticket nicht bearbeiten.")
return redirect(self.get_success_url())
return super().post(request, *args, **kwargs)
class AssignedTicketListView(LoginRequiredMixin, ListView): class AssignedTicketListView(LoginRequiredMixin, ListView):
model = Ticket model = Ticket