feat: added new Course class to model
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Ticket
|
from .models import Ticket, Course
|
||||||
|
|
||||||
admin.site.register(Ticket)
|
admin.site.register(Ticket)
|
||||||
|
admin.site.register(Course)
|
||||||
|
|||||||
@@ -2,6 +2,35 @@ from django.db import models
|
|||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
|
class Course(models.Model):
|
||||||
|
"""Kurs-Model für Backend-Verwaltung"""
|
||||||
|
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"
|
||||||
|
description = models.TextField(blank=True, verbose_name="Beschreibung")
|
||||||
|
|
||||||
|
tutor = models.ForeignKey(
|
||||||
|
User,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Tutor",
|
||||||
|
related_name="courses_as_tutor"
|
||||||
|
)
|
||||||
|
|
||||||
|
is_active = models.BooleanField(default=True, verbose_name="Aktiv")
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ['name']
|
||||||
|
verbose_name = "Kurs"
|
||||||
|
verbose_name_plural = "Kurse"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self.tutor:
|
||||||
|
return f"{self.code} - {self.name} (Tutor: {self.tutor.username})"
|
||||||
|
return f"{self.code} - {self.name}"
|
||||||
|
|
||||||
|
|
||||||
class Ticket(models.Model):
|
class Ticket(models.Model):
|
||||||
STATUS_CHOICES = [
|
STATUS_CHOICES = [
|
||||||
("new", "Neu"),
|
("new", "Neu"),
|
||||||
@@ -20,8 +49,13 @@ class Ticket(models.Model):
|
|||||||
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(
|
priority = models.CharField(max_length=10, choices=PRIORITY_CHOICES, default="medium")
|
||||||
max_length=10, choices=PRIORITY_CHOICES, default="medium"
|
|
||||||
|
course = models.ForeignKey(
|
||||||
|
Course,
|
||||||
|
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
|
||||||
|
|||||||
@@ -35,6 +35,17 @@
|
|||||||
{% 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 %}">{{ 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 %}bg-gray-100{% endif %}">{{ ticket.description }}</textarea>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Kurs:</label>
|
||||||
|
<select name="course"
|
||||||
|
{% 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 course in form.course.field.queryset %}
|
||||||
|
<option value="{{ course.pk }}"
|
||||||
|
{% if course.pk == ticket.course.pk %}selected{% endif %}>{{ course }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</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>
|
||||||
|
|||||||
@@ -1,127 +1,129 @@
|
|||||||
{% extends "ticketsystem/base.html" %}
|
{% extends "ticketsystem/base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<!-- Messages -->
|
||||||
<!-- Messages -->
|
{% if messages %}
|
||||||
{% if messages %}
|
<div class="max-w-4xl mx-auto px-4 pt-4">
|
||||||
<div class="max-w-4xl mx-auto px-4 pt-4">
|
{% for message in messages %}
|
||||||
{% for message in messages %}
|
<div class="mb-4 p-3 rounded {% if message.tags == 'error' %}bg-red-100 text-red-700{% elif message.tags == 'success' %}bg-green-100 text-green-700{% else %}bg-yellow-100 text-yellow-700{% endif %}">
|
||||||
<div class="mb-4 p-3 rounded {% if message.tags == 'error' %}bg-red-100 text-red-700{% elif message.tags == 'success' %}bg-green-100 text-green-700{% else %}bg-yellow-100 text-yellow-700{% endif %}">
|
{{ message }}
|
||||||
{{ message }}
|
</div>
|
||||||
</div>
|
{% endfor %}
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="max-w-4xl mx-auto px-4 py-6">
|
|
||||||
<!-- Ticket Erstellen -->
|
|
||||||
<div class="bg-white rounded-lg shadow p-6">
|
|
||||||
<h1 class="text-3xl font-bold mb-4">➕ Neues Ticket erstellen</h1>
|
|
||||||
|
|
||||||
<div class="text-sm text-gray-500 mb-6">
|
|
||||||
Fülle die Felder aus um ein neues Ticket zu erstellen
|
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
<form method="post" class="space-y-4">
|
<div class="max-w-4xl mx-auto px-4 py-6">
|
||||||
{% csrf_token %}
|
<!-- Ticket Erstellen -->
|
||||||
|
<div class="bg-white rounded-lg shadow p-6">
|
||||||
<!-- Form Errors -->
|
<h1 class="text-3xl font-bold mb-4">➕ Neues Ticket erstellen</h1>
|
||||||
{% if form.non_field_errors %}
|
<div class="text-sm text-gray-500 mb-6">Fülle die Felder aus um ein neues Ticket zu erstellen</div>
|
||||||
<div class="bg-red-100 border border-red-200 rounded p-3">
|
<form method="post" class="space-y-4">
|
||||||
{{ form.non_field_errors }}
|
{% csrf_token %}
|
||||||
</div>
|
<!-- Form Errors -->
|
||||||
{% endif %}
|
{% if form.non_field_errors %}
|
||||||
|
<div class="bg-red-100 border border-red-200 rounded p-3">{{ form.non_field_errors }}</div>
|
||||||
<!-- Titel -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium mb-1">Titel: *</label>
|
|
||||||
<input type="text" name="title" value="{{ form.title.value|default:'' }}"
|
|
||||||
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"
|
|
||||||
placeholder="Kurze Beschreibung des Problems">
|
|
||||||
{% if form.title.errors %}
|
|
||||||
<div class="text-red-600 text-sm mt-1">{{ form.title.errors }}</div>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
<!-- Titel -->
|
||||||
|
|
||||||
<!-- Beschreibung -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium mb-1">Beschreibung: *</label>
|
|
||||||
<textarea name="description" rows="6"
|
|
||||||
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"
|
|
||||||
placeholder="Detaillierte Beschreibung des Problems oder der Anfrage">{{ form.description.value|default:'' }}</textarea>
|
|
||||||
{% if form.description.errors %}
|
|
||||||
<div class="text-red-600 text-sm mt-1">{{ form.description.errors }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Status und Priorität -->
|
|
||||||
<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">Titel: *</label>
|
||||||
<select name="status"
|
<input type="text"
|
||||||
disabled
|
name="title"
|
||||||
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">
|
value="{{ form.title.value|default:'' }}"
|
||||||
<option value="new" {% if form.status.value == 'new' or not form.status.value %}selected{% endif %}>Neu</option>
|
required
|
||||||
</select>
|
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"
|
||||||
<input type="hidden" name="status" value="new">
|
placeholder="Kurze Beschreibung des Problems">
|
||||||
{% if form.status.errors %}
|
{% if form.title.errors %}<div class="text-red-600 text-sm mt-1">{{ form.title.errors }}</div>{% endif %}
|
||||||
<div class="text-red-600 text-sm mt-1">{{ form.status.errors }}</div>
|
</div>
|
||||||
|
<!-- Beschreibung -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Beschreibung: *</label>
|
||||||
|
<textarea name="description"
|
||||||
|
rows="6"
|
||||||
|
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"
|
||||||
|
placeholder="Detaillierte Beschreibung des Problems oder der Anfrage">{{ form.description.value|default:'' }}</textarea>
|
||||||
|
{% if form.description.errors %}
|
||||||
|
<div class="text-red-600 text-sm mt-1">{{ form.description.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium mb-1">Priorität:</label>
|
<label class="block text-sm font-medium mb-1">Kurs: *</label>
|
||||||
<select name="priority"
|
<select name="course"
|
||||||
|
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="low" {% if form.priority.value == 'low' %}selected{% endif %}>Niedrig</option>
|
<option value="">Kurs auswählen...</option>
|
||||||
<option value="medium" {% if form.priority.value == 'medium' or not form.priority.value %}selected{% endif %}>Normal</option>
|
{% for course in form.course.field.queryset %}
|
||||||
<option value="high" {% if form.priority.value == 'high' %}selected{% endif %}>Hoch</option>
|
<option value="{{ course.pk }}"
|
||||||
|
{% if form.course.value == course.pk %}selected{% endif %}>{{ course }}</option>
|
||||||
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
{% if form.priority.errors %}
|
{% if form.course.errors %}<div class="text-red-600 text-sm mt-1">{{ form.course.errors }}</div>{% endif %}
|
||||||
<div class="text-red-600 text-sm mt-1">{{ form.priority.errors }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Status und Priorität -->
|
||||||
|
<div class="grid grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Status:</label>
|
||||||
|
<select name="status"
|
||||||
|
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">
|
||||||
|
<option value="new"
|
||||||
|
{% if form.status.value == 'new' or not form.status.value %}selected{% endif %}>
|
||||||
|
Neu
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<input type="hidden" name="status" value="new">
|
||||||
|
{% if form.status.errors %}<div class="text-red-600 text-sm mt-1">{{ form.status.errors }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Priorität:</label>
|
||||||
|
<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">
|
||||||
|
<option value="low" {% if form.priority.value == 'low' %}selected{% endif %}>Niedrig</option>
|
||||||
|
<option value="medium"
|
||||||
|
{% if form.priority.value == 'medium' or not form.priority.value %}selected{% endif %}>
|
||||||
|
Normal
|
||||||
|
</option>
|
||||||
|
<option value="high"
|
||||||
|
{% if form.priority.value == 'high' %}selected{% endif %}>Hoch</option>
|
||||||
|
</select>
|
||||||
|
{% if form.priority.errors %}<div class="text-red-600 text-sm mt-1">{{ form.priority.errors }}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Zugewiesen an -->
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium mb-1">Zugewiesen an:</label>
|
||||||
|
<select name="assigned_to"
|
||||||
|
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="">Nicht zugewiesen</option>
|
||||||
|
{% for user in form.assigned_to.field.queryset %}
|
||||||
|
<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>
|
||||||
|
<!-- Buttons -->
|
||||||
|
<div class="flex gap-4 pt-4">
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-green-500 hover:bg-green-600 text-white px-6 py-2 rounded font-medium">
|
||||||
|
✅ Ticket erstellen
|
||||||
|
</button>
|
||||||
|
<a href="{% url 'ticket-list' %}"
|
||||||
|
class="bg-gray-500 hover:bg-gray-600 text-white px-6 py-2 rounded font-medium">❌ Abbrechen</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<!-- Hilfe Text -->
|
||||||
|
<div class="mt-8 p-4 bg-blue-50 rounded border-l-4 border-blue-400">
|
||||||
|
<h3 class="font-medium text-blue-900 mb-2">💡 Tipps:</h3>
|
||||||
|
<ul class="text-sm text-blue-800 space-y-1">
|
||||||
|
<li>
|
||||||
|
• Verwende einen <strong>aussagekräftigen Titel</strong>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
• Beschreibe das Problem so <strong>detailliert wie möglich</strong>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Zugewiesen an -->
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium mb-1">Zugewiesen an:</label>
|
|
||||||
<select name="assigned_to"
|
|
||||||
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="">Nicht zugewiesen</option>
|
|
||||||
{% for user in form.assigned_to.field.queryset %}
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Buttons -->
|
|
||||||
<div class="flex gap-4 pt-4">
|
|
||||||
<button type="submit"
|
|
||||||
class="bg-green-500 hover:bg-green-600 text-white px-6 py-2 rounded font-medium">
|
|
||||||
✅ Ticket erstellen
|
|
||||||
</button>
|
|
||||||
<a href="{% url 'ticket-list' %}"
|
|
||||||
class="bg-gray-500 hover:bg-gray-600 text-white px-6 py-2 rounded font-medium">
|
|
||||||
❌ Abbrechen
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Hilfe Text -->
|
|
||||||
<div class="mt-8 p-4 bg-blue-50 rounded border-l-4 border-blue-400">
|
|
||||||
<h3 class="font-medium text-blue-900 mb-2">💡 Tipps:</h3>
|
|
||||||
<ul class="text-sm text-blue-800 space-y-1">
|
|
||||||
<li>• Verwende einen <strong>aussagekräftigen Titel</strong></li>
|
|
||||||
<li>• Beschreibe das Problem so <strong>detailliert wie möglich</strong></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endblock %}
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class TicketListView(ListView):
|
|||||||
|
|
||||||
class TicketDetailUpdateView(UpdateView):
|
class TicketDetailUpdateView(UpdateView):
|
||||||
model = Ticket
|
model = Ticket
|
||||||
fields = ["title", "description", "status", "priority", "assigned_to"]
|
fields = ["title", "description", "status", "priority", "assigned_to", "course"]
|
||||||
template_name = "ticketsystem/detail.html"
|
template_name = "ticketsystem/detail.html"
|
||||||
comment_form_class = CommentForm
|
comment_form_class = CommentForm
|
||||||
|
|
||||||
@@ -96,7 +96,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", "assigned_to"]
|
tracked_fields = ["title", "description", "status", "priority", "assigned_to", "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)
|
||||||
@@ -168,7 +168,7 @@ class AssignedTicketListView(LoginRequiredMixin, ListView):
|
|||||||
|
|
||||||
class TicketCreateView(CreateView):
|
class TicketCreateView(CreateView):
|
||||||
model = Ticket
|
model = Ticket
|
||||||
fields = ["title", "description", "status", "priority", "assigned_to"]
|
fields = ["title", "description", "status", "priority", "assigned_to", "course"]
|
||||||
template_name = "ticketsystem/ticket_form.html"
|
template_name = "ticketsystem/ticket_form.html"
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
@@ -182,7 +182,7 @@ class TicketCreateView(CreateView):
|
|||||||
|
|
||||||
class TicketUpdateView(LoginRequiredMixin, UpdateView):
|
class TicketUpdateView(LoginRequiredMixin, UpdateView):
|
||||||
model = Ticket
|
model = Ticket
|
||||||
fields = ["title", "description", "status", "priority", "assigned_to"]
|
fields = ["title", "description", "status", "priority", "assigned_to", "course"]
|
||||||
template_name = "ticketsystem/ticket_form.html"
|
template_name = "ticketsystem/ticket_form.html"
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
|||||||
Reference in New Issue
Block a user