Free and open source ticket system written in python
1from django.shortcuts import render, redirect
2from django.contrib.auth.decorators import login_required
3from django.shortcuts import get_object_or_404
4from .models import Ticket, Template, FileAttachment
5from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
6from django.db.models import Q
7from .forms import CommentForm, TicketForm, TemplateForm, TeamAssignmentForm, CategoryAssignmentForm
8from django.http import Http404, FileResponse
9from django.conf import settings
10import os
11
12
13@login_required
14def show_tickets(request):
15 tickets = Ticket.get_open_tickets(request.user).order_by("priority", "-updated_at")
16 return render(request, "ticketing/tickets.html", {"tickets": tickets})
17
18
19@login_required
20def show_tickets_history(request):
21 qs = Ticket.get_closed_tickets(request.user).order_by("priority", "-updated_at")
22
23 per_page = int(request.GET.get("per_page") or 20)
24 paginator = Paginator(qs, per_page)
25
26 page_number = request.GET.get("page") or 1
27 try:
28 page_obj = paginator.page(page_number)
29 except PageNotAnInteger:
30 page_obj = paginator.page(1)
31 except EmptyPage:
32 # Show last page if page is out of range
33 page_obj = paginator.page(paginator.num_pages)
34
35 context = {
36 "tickets": page_obj.object_list,
37 "page_obj": page_obj,
38 "paginator": paginator,
39 "is_paginated": page_obj.paginator.num_pages > 1,
40 "per_page": per_page,
41 }
42 print(paginator, page_obj)
43 return render(request, "ticketing/tickets_history.html", context)
44
45
46@login_required
47def show_ticket(request, ticket_id):
48 ticket = get_object_or_404(Ticket, pk=ticket_id)
49 can_edit = ticket.can_edit(request.user)
50 # comment_templates = Template.objects.filter(category=ticket.category)
51
52 if not ticket.can_open(request.user):
53 return redirect("all_tickets")
54
55 form, template_form, team_assignment_form, category_assignment_form = CommentForm(
56 ), TemplateForm(), TeamAssignmentForm(), CategoryAssignmentForm()
57
58 if request.method == "POST":
59 if 'apply_template' in request.POST and can_edit:
60 template_form = TemplateForm(request.POST)
61 if template_form.is_valid():
62 template = template_form.cleaned_data["template_select"]
63 form = CommentForm(initial={"text": template.content})
64 elif 'assign_to_team' in request.POST and can_edit:
65 team_assignment_form = TeamAssignmentForm(request.POST)
66 if team_assignment_form.is_valid():
67 ticket.assign_to_team(
68 team_assignment_form.cleaned_data["team_select"])
69 elif 'assign_to_category' in request.POST and can_edit:
70 category_assignment_form = CategoryAssignmentForm(request.POST)
71 if category_assignment_form.is_valid():
72 ticket.category = category_assignment_form.cleaned_data["category_select"]
73 ticket.save()
74 elif 'assign_self' in request.POST and can_edit:
75 ticket.assigned_to = request.user
76 ticket.save()
77 elif 'reopen_ticket' in request.POST and can_edit:
78 ticket.status = Ticket.Status.IN_PROGRESS
79 ticket.save()
80 else:
81 form = CommentForm(request.POST, request.FILES)
82 if form.is_valid():
83 ticket.comment_set.create(
84 user=request.user, ticket=ticket,
85 text=form.cleaned_data["text"], is_only_for_staff=form.cleaned_data["hidden_from_client"]
86 )
87 # Add attachments
88 if form.cleaned_data["attachments"]:
89 for file in form.cleaned_data["attachments"]:
90 ticket.fileattachment_set.create(file=file)
91
92 if 'close' in request.POST and can_edit:
93 ticket.close_ticket()
94 return redirect("ticket_detail", ticket_id=ticket.id)
95
96 comments = ticket.comment_set.all()
97 context = {
98 "ticket": ticket, "comments": comments, "attachments": ticket.fileattachment_set.all(),
99 "form": form, "template_form": template_form,
100 "team_assignment_form": team_assignment_form, "category_assignment_form": category_assignment_form,
101 "can_edit": can_edit
102 }
103 return render(request, "ticketing/ticket_detail.html", context)
104
105
106@login_required
107def create_ticket(request):
108
109 has_closed_tickets = Ticket.objects.filter(
110 user=request.user, status=Ticket.Status.CLOSED).exists()
111
112 if request.method == "POST":
113 form = TicketForm(request.user, request.POST, request.FILES)
114 if form.is_valid():
115 ticket = form.save(commit=False)
116 ticket.user = request.user
117 ticket.save()
118
119 # Add attachments
120 if form.cleaned_data["attachments"]:
121 for file in form.cleaned_data["attachments"]:
122 ticket.fileattachment_set.create(file=file)
123
124 return redirect("ticket_detail", ticket_id=ticket.id)
125 else:
126 form = TicketForm(request.user)
127 return render(request, "ticketing/create_ticket.html", {"form": form, "has_closed_tickets": has_closed_tickets})
128
129
130@login_required
131def dashboard(request):
132
133 tickets = Ticket.objects.all().order_by("-created_at")
134 open_tickets = tickets.filter(status=Ticket.Status.OPEN)
135 in_progress_tickets = tickets.filter(status=Ticket.Status.IN_PROGRESS)
136 closed_tickets = tickets.filter(status=Ticket.Status.CLOSED)
137 return render(request, "ticketing/dashboard.html", {"tickets": tickets, "open_tickets": open_tickets, "in_progress_tickets": in_progress_tickets, "closed_tickets": closed_tickets})
138
139
140@login_required
141def download_attachment(request, attachment_id):
142 """
143 View that checks if the user has permission
144 to access the ticket before serving the file.
145 """
146 attachment = get_object_or_404(FileAttachment, pk=attachment_id)
147 ticket = attachment.ticket
148
149 if not ticket.can_open(request.user):
150 raise Http404("File not found")
151
152 if not attachment.file:
153 raise Http404("File not found")
154
155 try:
156 file = attachment.file.open('rb')
157 response = FileResponse(file, content_type='application/octet-stream')
158
159 filename = os.path.basename(attachment.file.name)
160 response['Content-Disposition'] = f'inline; filename="{filename}"'
161 return response
162 except (ValueError, IOError, OSError):
163 # backwards compatibility
164 try:
165 old_path = os.path.join(settings.MEDIA_ROOT, attachment.file.name)
166 if os.path.exists(old_path):
167 file = open(old_path, 'rb')
168 response = FileResponse(file, content_type='application/octet-stream')
169 filename = os.path.basename(attachment.file.name)
170 response['Content-Disposition'] = f'inline; filename="{filename}"'
171 return response
172 except (ValueError, IOError, OSError):
173 pass
174 raise Http404("File not found")