Free and open source ticket system written in python

📦 NEW: Add follow-up ticket feature

+66 -9
+1 -1
paw/__init__.py
··· 1 1 from django import get_version 2 2 3 - VERSION = (0, 1, 0, "beta", 7) 3 + VERSION = (0, 1, 0, "beta", 8) 4 4 5 5 __version__ = get_version(VERSION)
+14 -3
paw/templates/ticketing/create_ticket.html
··· 7 7 {% csrf_token %} 8 8 {{ form.non_field_errors }} 9 9 <label class="form-control mb-2"> 10 + {{ form.category.errors }} 11 + <div class="label"> 12 + <span for="{{ form.category.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Category' %}</span> 13 + </div> 14 + {{ form.category }} 15 + </label> 16 + <label class="form-control mb-2"> 10 17 {{ form.title.errors }} 11 18 <div class="label"> 12 19 <span for="{{ form.title.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Title' %}</span> ··· 20 27 </div> 21 28 {{ form.description }} 22 29 </label> 30 + 31 + {% if has_closed_tickets %} 23 32 <label class="form-control mb-2"> 24 - {{ form.category.errors }} 33 + {{ form.follow_up_to.errors }} 25 34 <div class="label"> 26 - <span for="{{ form.category.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Category' %}</span> 35 + <span for="{{ form.follow_up_to.id_for_label }}" class="label-text font-semibold text-base-content">{% trans 'Create as follow-up to a closed ticket' %}</span> 27 36 </div> 28 - {{ form.category }} 37 + {{ form.follow_up_to }} 29 38 </label> 39 + {% endif %} 40 + 30 41 <div class="flex justify-end items-center mt-4"> 31 42 <button type="submit" class="btn btn-success">{% trans 'Create Ticket' %}</button> 32 43 </div>
+6 -2
ticketing/forms.py
··· 13 13 class TicketForm(forms.ModelForm): 14 14 class Meta: 15 15 model = Ticket 16 - fields = ['title', 'description', 'category'] 16 + fields = ['title', 'description', 'category', 'follow_up_to'] 17 17 widgets = { 18 18 'title': forms.TextInput(attrs={'class': 'input input-bordered w-full', 'placeholder': _('Please enter a title'), 'aria-label': _('Title')}), 19 19 'description': forms.Textarea(attrs={'class': 'textarea textarea-bordered h-32 w-full', 'placeholder': _('Please describe your issue'), 'aria-label': _('Description')}), 20 20 'category': forms.Select(attrs={'class': 'select select-bordered w-full'}), 21 + 'follow_up_to': forms.Select(attrs={'class': 'select select-bordered w-full'}), 21 22 } 22 23 23 - def __init__(self, *args, **kwargs): 24 + def __init__(self, user, *args, **kwargs): 24 25 super(TicketForm, self).__init__(*args, **kwargs) 25 26 self.fields['category'].empty_label = _('General') 27 + self.fields['follow_up_to'].empty_label = _('No Follow-up') 28 + self.fields['follow_up_to'].queryset = Ticket.objects.filter( 29 + status=Ticket.Status.CLOSED, user=user) 26 30 27 31 28 32 class TemplateForm(forms.Form):
+36
ticketing/migrations/0009_ticket_follow_up_to_alter_category_team.py
··· 1 + # Generated by Django 5.0.3 on 2024-03-09 22:59 2 + 3 + import django.db.models.deletion 4 + from django.db import migrations, models 5 + 6 + 7 + class Migration(migrations.Migration): 8 + 9 + dependencies = [ 10 + ("ticketing", "0008_remove_ticket_assigned_group_team_category_team_and_more"), 11 + ] 12 + 13 + operations = [ 14 + migrations.AddField( 15 + model_name="ticket", 16 + name="follow_up_to", 17 + field=models.ForeignKey( 18 + blank=True, 19 + null=True, 20 + on_delete=django.db.models.deletion.CASCADE, 21 + related_name="follow_ups", 22 + to="ticketing.ticket", 23 + ), 24 + ), 25 + migrations.AlterField( 26 + model_name="category", 27 + name="team", 28 + field=models.ForeignKey( 29 + blank=True, 30 + help_text="If a team is selected, new tickets will automatically assigned to this team.", 31 + null=True, 32 + on_delete=django.db.models.deletion.CASCADE, 33 + to="ticketing.team", 34 + ), 35 + ), 36 + ]
+2
ticketing/models.py
··· 51 51 PawUser, on_delete=models.CASCADE, related_name='assigned_to_user', null=True, blank=True) 52 52 assigned_team = models.ForeignKey( 53 53 Team, on_delete=models.CASCADE, related_name='assigned_to_team', null=True, blank=True) 54 + follow_up_to = models.ForeignKey( 55 + "self", on_delete=models.CASCADE, null=True, blank=True, related_name='follow_ups') 54 56 55 57 class Meta: 56 58 indexes = [
+7 -3
ticketing/views.py
··· 85 85 86 86 @login_required 87 87 def create_ticket(request): 88 + 89 + has_closed_tickets = Ticket.objects.filter( 90 + user=request.user, status=Ticket.Status.CLOSED).exists() 91 + 88 92 if request.method == "POST": 89 - form = TicketForm(request.POST) 93 + form = TicketForm(request.user, request.POST) 90 94 if form.is_valid(): 91 95 ticket = form.save(commit=False) 92 96 ticket.user = request.user 93 97 ticket.save() 94 98 return redirect("ticket_detail", ticket_id=ticket.id) 95 99 else: 96 - form = TicketForm() 97 - return render(request, "ticketing/create_ticket.html", {"form": form}) 100 + form = TicketForm(request.user) 101 + return render(request, "ticketing/create_ticket.html", {"form": form, "has_closed_tickets": has_closed_tickets}) 98 102 99 103 100 104 @login_required