init
This commit is contained in:
0
backend/servicedesk/__init__.py
Normal file
0
backend/servicedesk/__init__.py
Normal file
30
backend/servicedesk/admin.py
Normal file
30
backend/servicedesk/admin.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from django.contrib import admin
|
||||
from .models import ServiceTicket
|
||||
from trznice.admin import custom_admin_site
|
||||
|
||||
|
||||
class ServiceTicketAdmin(admin.ModelAdmin):
|
||||
list_display = ("id", "title", "status", "user", "created_at", "is_deleted")
|
||||
list_filter = ("status", "is_deleted")
|
||||
search_fields = ("title", "description", "user__username", "user__email")
|
||||
ordering = ("-created_at",)
|
||||
|
||||
readonly_fields = ['created_at']
|
||||
base_fields = ['title', 'category', 'description', 'user', 'status', 'created_at']
|
||||
|
||||
|
||||
def get_fields(self, request, obj=None):
|
||||
fields = self.base_fields.copy()
|
||||
if request.user.role == "admin":
|
||||
fields += ['is_deleted', 'deleted_at']
|
||||
return fields
|
||||
|
||||
def get_queryset(self, request):
|
||||
# Use the all_objects manager to show even soft-deleted entries
|
||||
if request.user.role == "admin":
|
||||
qs = self.model.all_objects.all()
|
||||
else:
|
||||
qs = self.model.objects.all()
|
||||
return qs
|
||||
|
||||
custom_admin_site.register(ServiceTicket, ServiceTicketAdmin)
|
||||
6
backend/servicedesk/apps.py
Normal file
6
backend/servicedesk/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class ServicedeskConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'servicedesk'
|
||||
11
backend/servicedesk/filters.py
Normal file
11
backend/servicedesk/filters.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import django_filters
|
||||
from .models import ServiceTicket
|
||||
|
||||
class ServiceTicketFilter(django_filters.FilterSet):
|
||||
user = django_filters.NumberFilter(field_name="user__id")
|
||||
status = django_filters.ChoiceFilter(choices=ServiceTicket.STATUS_CHOICES)
|
||||
category = django_filters.ChoiceFilter(choices=ServiceTicket.CATEGORY_CHOICES)
|
||||
|
||||
class Meta:
|
||||
model = ServiceTicket
|
||||
fields = ["user", "status", "category"]
|
||||
34
backend/servicedesk/migrations/0001_initial.py
Normal file
34
backend/servicedesk/migrations/0001_initial.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.2.4 on 2025-08-07 15:13
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ServiceTicket',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('is_deleted', models.BooleanField(default=False)),
|
||||
('deleted_at', models.DateTimeField(blank=True, null=True)),
|
||||
('title', models.CharField(max_length=255, verbose_name='Název')),
|
||||
('description', models.TextField(blank=True, null=True, verbose_name='Popis problému')),
|
||||
('status', models.CharField(blank=True, choices=[('new', 'Nový'), ('in_progress', 'Řeší se'), ('resolved', 'Vyřešeno'), ('closed', 'Uzavřeno')], default='new', max_length=20, verbose_name='Stav')),
|
||||
('category', models.CharField(blank=True, choices=[('tech', 'Technická chyba'), ('reservation', 'Chyba při rezervaci'), ('payment', 'Problém s platbou'), ('account', 'Problém s účtem'), ('content', 'Nesrovnalost v obsahu'), ('suggestion', 'Návrh na zlepšení'), ('other', 'Jiný')], default='tech', max_length=20, verbose_name='Kategorie')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Datum')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='tickets', to=settings.AUTH_USER_MODEL, verbose_name='Zadavatel')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
0
backend/servicedesk/migrations/__init__.py
Normal file
0
backend/servicedesk/migrations/__init__.py
Normal file
32
backend/servicedesk/models.py
Normal file
32
backend/servicedesk/models.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from trznice.models import SoftDeleteModel
|
||||
|
||||
class ServiceTicket(SoftDeleteModel):
|
||||
STATUS_CHOICES = [
|
||||
("new", "Nový"),
|
||||
("in_progress", "Řeší se"),
|
||||
("resolved", "Vyřešeno"),
|
||||
("closed", "Uzavřeno"),
|
||||
]
|
||||
|
||||
CATEGORY_CHOICES = [
|
||||
("tech", "Technická chyba"),
|
||||
("reservation", "Chyba při rezervaci"),
|
||||
("payment", "Problém s platbou"),
|
||||
("account", "Problém s účtem"),
|
||||
("content", "Nesrovnalost v obsahu"),
|
||||
("suggestion", "Návrh na zlepšení"),
|
||||
("other", "Jiný"),
|
||||
]
|
||||
|
||||
title = models.CharField(max_length=255, verbose_name="Název")
|
||||
description = models.TextField(verbose_name="Popis problému", null=True, blank=True)
|
||||
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, verbose_name="Zadavatel", related_name="tickets", null=False, blank=False)
|
||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="new", verbose_name="Stav", blank=True)
|
||||
category = models.CharField(max_length=20, choices=CATEGORY_CHOICES, default="tech", verbose_name="Kategorie", blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Datum", editable=False)
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title} ({self.get_status_display()})"
|
||||
|
||||
47
backend/servicedesk/serializers.py
Normal file
47
backend/servicedesk/serializers.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from .models import ServiceTicket
|
||||
from account.models import CustomUser
|
||||
|
||||
|
||||
class ServiceTicketSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = ServiceTicket
|
||||
fields = [
|
||||
"id", "title", "description", "user",
|
||||
"status", "category", "created_at"
|
||||
]
|
||||
read_only_fields = ["id", "created_at"]
|
||||
|
||||
extra_kwargs = {
|
||||
"title": {"help_text": "Stručný název požadavku", "required": True},
|
||||
"description": {"help_text": "Detailní popis problému", "required": False},
|
||||
"user": {"help_text": "ID uživatele, který požadavek zadává", "required": True},
|
||||
"status": {"help_text": "Stav požadavku (new / in_progress / resolved / closed)", "required": False},
|
||||
"category": {"help_text": "Kategorie požadavku (tech / reservation / payment / account / content / suggestion / other)", "required": True},
|
||||
}
|
||||
|
||||
def validate(self, data):
|
||||
user = data.get("user", None)
|
||||
|
||||
# if user is None:
|
||||
# raise serializers.ValidationError("Product is a required field.")
|
||||
# # Check if user exists in DB
|
||||
# if not CustomUser.objects.filter(pk=user.pk if hasattr(user, 'pk') else user).exists():
|
||||
# raise serializers.ValidationError("Neplatné ID Užívatele.")
|
||||
|
||||
# Example validation: status must be one of the defined choices
|
||||
if "status" in data and data["status"] not in dict(ServiceTicket.STATUS_CHOICES):
|
||||
raise serializers.ValidationError({"status": "Neplatný stav požadavku."})
|
||||
|
||||
if "category" in data and data["category"] not in dict(ServiceTicket.CATEGORY_CHOICES):
|
||||
raise serializers.ValidationError({"category": "Neplatná kategorie požadavku."})
|
||||
|
||||
title = data.get("title", "").strip()
|
||||
if not title:
|
||||
raise serializers.ValidationError("Název požadavku nemůže být prázdný.")
|
||||
if len(title) > 255:
|
||||
raise serializers.ValidationError("Název požadavku nemůže být delší než 255 znaků.")
|
||||
data["title"] = title # Optional: overwrite with trimmed version
|
||||
|
||||
return data
|
||||
3
backend/servicedesk/tests.py
Normal file
3
backend/servicedesk/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
10
backend/servicedesk/urls.py
Normal file
10
backend/servicedesk/urls.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from django.urls import path, include
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from .views import ServiceTicketViewSet
|
||||
|
||||
router = DefaultRouter()
|
||||
router.register(r'', ServiceTicketViewSet, basename='tickets')
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(router.urls)),
|
||||
]
|
||||
84
backend/servicedesk/views.py
Normal file
84
backend/servicedesk/views.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from rest_framework import viewsets, filters
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from django.contrib.auth import get_user_model
|
||||
|
||||
from .models import ServiceTicket
|
||||
from .serializers import ServiceTicketSerializer
|
||||
from .filters import ServiceTicketFilter
|
||||
from account.email import send_email_with_context
|
||||
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.exceptions import PermissionDenied
|
||||
|
||||
# from account.permissions import RoleAllowed
|
||||
|
||||
|
||||
@extend_schema(
|
||||
tags=["ServiceTicket"],
|
||||
description="Správa uživatelských požadavků – vytvoření, úprava a výpis. Filtrování podle stavu, urgence, uživatele atd."
|
||||
)
|
||||
class ServiceTicketViewSet(viewsets.ModelViewSet):
|
||||
# queryset = ServiceTicket.objects.select_related("user").all().order_by("-created_at")
|
||||
queryset = ServiceTicket.objects.all().order_by("-created_at")
|
||||
serializer_class = ServiceTicketSerializer
|
||||
filter_backends = [DjangoFilterBackend, filters.OrderingFilter, filters.SearchFilter]
|
||||
filterset_class = ServiceTicketFilter
|
||||
ordering_fields = ["created_at"]
|
||||
search_fields = ["title", "description", "user__username"]
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
user = self.request.user
|
||||
if user.role in ["admin", "cityClerk"]: # Adjust as needed for staff roles
|
||||
# return ServiceTicket.objects.select_related("user").all().order_by("-created_at")
|
||||
return ServiceTicket.objects.all().order_by("-created_at")
|
||||
else:
|
||||
# return ServiceTicket.objects.select_related("user").filter(user=user).order_by("-created_at")
|
||||
return ServiceTicket.objects.filter(user=user).order_by("-created_at")
|
||||
|
||||
def get_object(self):
|
||||
obj = super().get_object()
|
||||
if self.request.user.role not in ["admin", "cityClerk"] and obj.user != self.request.user:
|
||||
raise PermissionDenied("Nemáte oprávnění pracovat s tímto požadavkem.")
|
||||
return obj
|
||||
|
||||
def perform_create(self, serializer):
|
||||
user_request = serializer.save(user=self.request.user)
|
||||
|
||||
# Map categories to roles responsible for handling them
|
||||
category_role_map = {
|
||||
"tech": "admin",
|
||||
"reservation": "cityClerk",
|
||||
"payment": "admin",
|
||||
"account": "admin",
|
||||
"content": "admin",
|
||||
"suggestion": "admin",
|
||||
"other": "admin"
|
||||
}
|
||||
|
||||
role = category_role_map.get(user_request.category)
|
||||
if not role:
|
||||
return # Or log: unknown category, no notification sent
|
||||
|
||||
User = get_user_model()
|
||||
recipients = User.objects.filter(role=role, email__isnull=False).exclude(email="").values_list("email", flat=True)
|
||||
|
||||
if not recipients:
|
||||
recipients = User.objects.filter(role='admin', email__isnull=False).exclude(email="").values_list("email", flat=True)
|
||||
if not recipients:
|
||||
return
|
||||
|
||||
subject = "Nový uživatelský požadavek"
|
||||
message = f"""
|
||||
Nový požadavek byl vytvořen:
|
||||
|
||||
Název: {user_request.title}
|
||||
Kategorie: {user_request.get_category_display()}
|
||||
Popis: {user_request.description or "—"}
|
||||
Vytvořeno: {user_request.created_at.strftime('%d.%m.%Y %H:%M')}
|
||||
Zadal: {user_request.user.get_full_name()} ({user_request.user.email})
|
||||
|
||||
Spravujte požadavky v systému.
|
||||
"""
|
||||
send_email_with_context(list(recipients), subject, message)
|
||||
Reference in New Issue
Block a user