Free and open source ticket system written in python

feat: add ticketing api

Signed-off-by: A. Ottr <alex@otter.foo>

+98
+7
paw/urls.py
··· 18 from django.contrib import admin 19 from django.urls import path, include 20 from django.conf.urls.static import static 21 22 urlpatterns = [ 23 path('i18n/', include('django.conf.urls.i18n')), 24 path("admin/", admin.site.urls), 25 path("accounts/", include("django.contrib.auth.urls")), 26 path("", include("core.urls")), 27 path("", include("ticketing.urls")), 28 path("status", include("status.urls")),
··· 18 from django.contrib import admin 19 from django.urls import path, include 20 from django.conf.urls.static import static 21 + from ninja import NinjaAPI 22 + from ninja.security import django_auth 23 + from ticketing.api import router as ticketing_router 24 + 25 + api = NinjaAPI(auth=django_auth) 26 + api.add_router("/tickets", ticketing_router) 27 28 urlpatterns = [ 29 path('i18n/', include('django.conf.urls.i18n')), 30 path("admin/", admin.site.urls), 31 path("accounts/", include("django.contrib.auth.urls")), 32 + path("api/", api.urls), # Mount the API at /api/ 33 path("", include("core.urls")), 34 path("", include("ticketing.urls")), 35 path("status", include("status.urls")),
+39
ticketing/api.py
···
··· 1 + """ 2 + API routes for the ticketing app. 3 + """ 4 + from ninja import Router, ModelSchema, Schema 5 + from ninja.orm import create_schema 6 + from django.conf import settings 7 + from django.shortcuts import get_object_or_404 8 + from .models import Ticket, Category 9 + from core.models import PawUser 10 + from .schemas import TicketSchema, TicketDetailSchema, CommentSchema 11 + 12 + router = Router(tags=["tickets"]) 13 + 14 + 15 + @router.get("/", response=list[TicketSchema]) 16 + def list_tickets(request): 17 + """List all tickets.""" 18 + return Ticket.get_open_tickets(request.user).order_by("priority", "-updated_at").all() 19 + 20 + @router.get("/history", response=list[TicketSchema]) 21 + def list_tickets_history(request): 22 + """List all closed tickets.""" 23 + return Ticket.get_closed_tickets(request.user).order_by("priority", "-updated_at").all() 24 + 25 + @router.get("/{ticket_id}", response=TicketDetailSchema) 26 + def get_ticket(request, ticket_id: int): 27 + """Get a specific ticket.""" 28 + # TODO: Implement ticket retrieval logic 29 + return get_object_or_404(Ticket, id=ticket_id) 30 + 31 + @router.get("/{ticket_id}/comments", response=list[CommentSchema]) 32 + def get_ticket_comments(request, ticket_id: int): 33 + """Get all comments for a specific ticket.""" 34 + ticket = get_object_or_404(Ticket, id=ticket_id) 35 + can_view_internal_comments = ticket.can_edit(request.user) 36 + if can_view_internal_comments: 37 + return ticket.comment_set.all() 38 + else: 39 + return ticket.comment_set.filter(is_only_for_staff=False).all()
+52
ticketing/schemas.py
···
··· 1 + from ninja import ModelSchema 2 + from ninja.orm import create_schema 3 + from .models import Ticket, Category, Comment, FileAttachment 4 + from core.models import PawUser 5 + 6 + class UserSchema(ModelSchema): 7 + class Meta: 8 + model = PawUser 9 + fields = ['id', 'username', 'profile_picture'] 10 + 11 + class CategorySchema(ModelSchema): 12 + class Meta: 13 + model = Category 14 + fields = ['id', 'name'] 15 + 16 + class FileAttachmentSchema(ModelSchema): 17 + class Meta: 18 + model = FileAttachment 19 + fields = ['id', 'file', 'uploaded_at'] 20 + 21 + class TicketSchema(ModelSchema): 22 + """Schema for ticket with nested user information.""" 23 + user: UserSchema 24 + assigned_to: UserSchema | None = None 25 + category: CategorySchema | None = None 26 + 27 + class Meta: 28 + model = Ticket 29 + fields = ['id', 'title', 'user', 'assigned_to', 'category', 'status', 'priority', 'created_at', 'updated_at'] 30 + 31 + class TicketDetailSchema(ModelSchema): 32 + user: UserSchema 33 + assigned_to: UserSchema | None = None 34 + category: CategorySchema | None = None 35 + follow_up_to: TicketSchema | None = None 36 + followed_up_by: list[TicketSchema] = [] 37 + attachments: list[FileAttachmentSchema] = [] 38 + 39 + class Meta: 40 + model = Ticket 41 + fields = ['id', 'title', 'user', 'assigned_to', 'category', 'status', 'priority', 'created_at', 'updated_at', 'description', 'follow_up_to'] 42 + 43 + @staticmethod 44 + def resolve_attachments(obj: Ticket): 45 + """Resolve attachments from reverse ForeignKey relationship.""" 46 + return obj.fileattachment_set.all() 47 + 48 + class CommentSchema(ModelSchema): 49 + user: UserSchema 50 + class Meta: 51 + model = Comment 52 + fields = ['id', 'user', 'text', 'created_at']