Free and open source ticket system written in python

Allow ReadOnly-Roles

+36 -10
+8 -8
paw/templates/ticketing/ticket_detail.html
··· 36 36 <h1 class="text-xl font-bold my-4">{% trans 'Activity' %}</h1> 37 37 <div class="mb-4"> 38 38 {% for comment in comments %} 39 - {% if not comment.is_only_for_staff or comment.is_only_for_staff and request.user.is_staff %} 39 + {% if not comment.is_only_for_staff or comment.is_only_for_staff and can_edit %} 40 40 {% if comment.user == request.user %} 41 41 <div class="chat chat-start"> 42 42 {% else %} ··· 62 62 </div> 63 63 <div class="mb-10"> 64 64 {% if ticket.status != 'closed' %} 65 - {% if request.user.is_staff %} 65 + {% if can_edit %} 66 66 <form action="" method="post"> 67 67 {% csrf_token %} 68 68 <div class="flex justify-end items-center mt-4 mb-2"> ··· 88 88 </div> 89 89 <div class="grow"></div> 90 90 <button type="submit" name="submit" class="btn btn-success">{% trans 'Add Comment' %}</button> 91 - {% if request.user.is_staff %} 91 + {% if can_edit %} 92 92 <button type="submit" name="close" class="btn btn-error ml-4">{% trans 'Close Ticket' %}</button> 93 93 {% endif %} 94 94 </div> ··· 106 106 </form> 107 107 {% else %} 108 108 <div class="divider">{% trans 'Ticket has been closed' %}</div> 109 - {% if request.user.is_staff and ticket.status == 'closed' %} 109 + {% if can_edit and ticket.status == 'closed' %} 110 110 <div class="flex justify-end items-center mt-4"> 111 111 <form method="post"> 112 112 {% csrf_token %} 113 113 <button class="btn btn-warning ml-2" name="reopen_ticket">{% trans 'Re-Open Ticket' %}</button> 114 114 </form> 115 115 </div> 116 - {% endif %} {% comment %} request.user.is_staff and ticket.status == 'closed' {% endcomment %} 116 + {% endif %} {% comment %} can_edit and ticket.status == 'closed' {% endcomment %} 117 117 {% endif %} 118 118 </div> 119 119 </div> ··· 162 162 <span>{% trans 'General' %}</span> 163 163 {% endif %} 164 164 </div> 165 - {% if request.user.is_staff %} 165 + {% if can_edit %} 166 166 <form action="" method="post"> 167 167 {% csrf_token %} 168 168 <h2 class="font-semibold text-xs mb-2">{% trans 'Assign to new category' %}</h2> ··· 177 177 <div class="text-base-content/85 flex items-center text-sm mb-4"> 178 178 <svg xmlns="http://www.w3.org/2000/svg" class="mr-2 w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0" /><path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /></svg> 179 179 {% include 'partials/assigned_to.html' with assigned_to=ticket.assigned_to %} 180 - {% if request.user.is_staff and ticket.assigned_to != request.user %} 180 + {% if can_edit and ticket.assigned_to != request.user %} 181 181 <form method="post"> 182 182 {% csrf_token %} 183 183 <button class="btn btn-xs btn-neutral ml-2" name="assign_self">{% trans 'Assign to me' %}</button> ··· 192 192 <span class="italic">{% trans 'Unassigned' %}</span> 193 193 {% endif %} 194 194 </div> 195 - {% if request.user.is_staff %} 195 + {% if can_edit %} 196 196 <form action="" method="post"> 197 197 {% csrf_token %} 198 198 <h2 class="font-semibold text-xs mb-2">{% trans 'Assign to new team' %}</h2>
+18
ticketing/migrations/0014_team_readonly_access.py
··· 1 + # Generated by Django 5.0.3 on 2024-03-24 18:30 2 + 3 + from django.db import migrations, models 4 + 5 + 6 + class Migration(migrations.Migration): 7 + 8 + dependencies = [ 9 + ("ticketing", "0013_team_access_non_category_tickets"), 10 + ] 11 + 12 + operations = [ 13 + migrations.AddField( 14 + model_name="team", 15 + name="readonly_access", 16 + field=models.BooleanField(default=False), 17 + ), 18 + ]
+8 -1
ticketing/models.py
··· 19 19 description = models.TextField(blank=True) 20 20 members = models.ManyToManyField(PawUser) 21 21 access_non_category_tickets = models.BooleanField(default=False) 22 + readonly_access = models.BooleanField(default=False) 22 23 23 24 def __str__(self): 24 25 return self.name ··· 102 103 return cls._get_tickets(user).filter(status=cls.Status.CLOSED) 103 104 104 105 def can_open(self, user): 105 - return self in Ticket._get_tickets(user) 106 + return self.user == user or self.assigned_to == user or self.assigned_team in user.team_set.all() or self.assigned_team is None and user.team_set.filter(access_non_category_tickets=True).exists() 107 + 108 + def can_edit(self, user): 109 + assigned_and_write_access = self.assigned_team in user.team_set.filter(readonly_access=False) 110 + unassigned_and_write_access = self.assigned_team is None and user.team_set.filter(access_non_category_tickets=True, readonly_access=False).exists() 111 + print(assigned_and_write_access, unassigned_and_write_access) 112 + return self.can_open(user) and (assigned_and_write_access or unassigned_and_write_access) 106 113 107 114 108 115 def close_ticket(self):
+2 -1
ticketing/views.py
··· 76 76 context = { 77 77 "ticket": ticket, "comments": comments, "attachments": [attachment.file for attachment in ticket.fileattachment_set.all()], 78 78 "form": form, "template_form": template_form, 79 - "team_assignment_form": team_assignment_form, "category_assignment_form": category_assignment_form 79 + "team_assignment_form": team_assignment_form, "category_assignment_form": category_assignment_form, 80 + "can_edit": ticket.can_edit(request.user) 80 81 } 81 82 return render(request, "ticketing/ticket_detail.html", context) 82 83