diff --git a/apps/authentication/migrations/0002_auto_20240112_1604.py b/apps/authentication/migrations/0002_auto_20240112_1604.py new file mode 100644 index 0000000..29e6d97 --- /dev/null +++ b/apps/authentication/migrations/0002_auto_20240112_1604.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.16 on 2024-01-12 16:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='department', + old_name='id', + new_name='uuid', + ), + migrations.RenameField( + model_name='user', + old_name='id', + new_name='uuid', + ), + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index 0f821aa..fd41278 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -1,6 +1,6 @@ # -*- encoding: utf-8 -*- -import uuid +from uuid import uuid4 import os from django.db import models @@ -23,11 +23,11 @@ class OverwriteStorage(FileSystemStorage): @deconstructible class IconPathGenerator: def __call__(self, instance, filename: str) -> str: - return os.path.join("users", str(instance.id), "icon.webp") + return os.path.join("users", str(instance.uuid), "icon.webp") class Department(models.Model): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False) title = models.CharField(max_length=150) icon = models.CharField(max_length=32, null=True, blank=True) @@ -35,12 +35,6 @@ class Department(models.Model): def __str__(self): return self.title - def serialize(self) -> dict: - return { - "title": self.title, - "icon": self.icon - } - class UserManager(BaseUserManager): @@ -70,7 +64,7 @@ class UserManager(BaseUserManager): class User(AbstractBaseUser, PermissionsMixin): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False) icon = models.ImageField( _("profile picture"), @@ -145,7 +139,7 @@ class User(AbstractBaseUser, PermissionsMixin): verbose_name_plural = _('users') def __str__(self): - return f"{self.id} • {self.email} • {self.formal_fullname}" + return f"{self.uuid} • {self.email} • {self.formal_fullname}" def save(self, *args, **kwargs): self.edit_timestamp = timezone.now() @@ -162,17 +156,3 @@ class User(AbstractBaseUser, PermissionsMixin): @property def formal_fullname(self) -> str: return f"{self.surname}, {self.forename}" - - def serialize(self) -> dict: - department = self.department.serialize() if self.department else None - - return { - "id": self.id, - "icon": self.icon.url, - "email": self.email, - "forename": self.forename, - "surname": self.surname, - "department": department, - "create_timestamp": self.create_timestamp, - "edit_timestamp": self.edit_timestamp - } diff --git a/apps/home/config.py b/apps/home/config.py index cb99bb5..21a9ac2 100644 --- a/apps/home/config.py +++ b/apps/home/config.py @@ -1,7 +1,4 @@ # -*- encoding: utf-8 -*- -""" -Copyright (c) 2019 - present AppSeed.us -""" from django.apps import AppConfig diff --git a/apps/home/migrations/0002_auto_20240112_1604.py b/apps/home/migrations/0002_auto_20240112_1604.py new file mode 100644 index 0000000..0e7b4f8 --- /dev/null +++ b/apps/home/migrations/0002_auto_20240112_1604.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.16 on 2024-01-12 16:04 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('home', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='ticket', + old_name='id', + new_name='uuid', + ), + migrations.RenameField( + model_name='ticketpriority', + old_name='id', + new_name='uuid', + ), + migrations.RenameField( + model_name='tickettag', + old_name='id', + new_name='uuid', + ), + ] diff --git a/apps/home/models.py b/apps/home/models.py index ef3bc44..ff19a44 100644 --- a/apps/home/models.py +++ b/apps/home/models.py @@ -1,7 +1,7 @@ # -*- encoding: utf-8 -*- -import uuid import bleach +from uuid import uuid4 from datetime import timedelta, datetime from django.db import models @@ -11,7 +11,7 @@ from django.utils.translation import gettext_lazy as _ class TicketPriority(models.Model): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False) title = models.CharField(max_length=32) colour = models.CharField(max_length=7) @@ -20,17 +20,9 @@ class TicketPriority(models.Model): def __str__(self): return self.title - def serialize(self) -> dict: - return { - "id": self.id, - "title": self.title, - "colour": self.colour, - "backgroundcolour": self.backgroundcolour - } - class TicketTag(models.Model): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False) title = models.CharField(max_length=32) colour = models.CharField(max_length=7) @@ -39,66 +31,68 @@ class TicketTag(models.Model): def __str__(self): return self.title - def serialize(self) -> dict: - return { - "id": self.id, - "title": self.title, - "colour": self.colour, - "backgroundcolour": self.backgroundcolour - } - class Ticket(models.Model): - id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) + """Represents a Ticket used to communicate issues or questions.""" + + uuid = models.UUIDField(primary_key=True, default=uuid4, editable=False) + + # Main Attributes title = models.CharField( - _("title"), + verbose_name=_("title"), + help_text=_("An extremely short summary of the ticket subject."), max_length=100, - help_text=_("An extremely short summary of the ticket subject.") ) description = models.TextField( - _("description"), + verbose_name=_("description"), + help_text=_("Detailed description of the ticket subject."), max_length=650, - help_text=_("Detailed description of the ticket subject.") ) + + # Dirty Foreigers + author = models.ForeignKey( settings.AUTH_USER_MODEL, verbose_name=_("author"), + help_text=_("The creator of the ticket."), on_delete=models.CASCADE, - help_text=_("The creator of the ticket.") ) - priority = models.ForeignKey( TicketPriority, verbose_name=_("priority"), + help_text=_("The importance level of this ticket."), on_delete=models.CASCADE, - help_text=_("The importance level of this ticket.") ) tags = models.ManyToManyField( TicketTag, verbose_name=_("tags"), + help_text=_("Categories of the ticket."), blank=True, - help_text=_("Categories of the ticket.") ) + # Timestamps + create_timestamp = models.DateTimeField( - _("Creation Date"), + verbose_name=_("Creation Date"), + help_text=_("When the user was created."), editable=True, default=timezone.now, - help_text=_("When the user was created.") ) edit_timestamp = models.DateTimeField( - _("Last Edited"), + verbose_name=_("Last Edited"), + help_text=_("When the user was last edited."), editable=True, default=timezone.now, - help_text=_("When the user was last edited.") ) def __str__(self): - return f"#{self.id} • {self.title} •{f' {self.author.department.title} •' if self.author.department else ''} {self.author.formal_fullname}" + return f"#{self.uuid} • {self.title} •{f' {self.author.department.title} •' if self.author.department else ''} {self.author.formal_fullname}" def clean_description(self): + """Sanitise the description as it may contain some allowed HTML tags.""" + cleaned_description = bleach.clean( self.description, tags=[ @@ -110,13 +104,14 @@ class Ticket(models.Model): return cleaned_description def save(self, *args, **kwargs): + """Override the save method to clean the description and apply timestamps.""" + self.description = self.clean_description() - # we must use the same datetime object, otherwise they wont match now = timezone.now() - - if self._state.adding: self.create_timestamp = now self.edit_timestamp = now + if self._state.adding: + self.create_timestamp = now super().save(*args, **kwargs) @@ -174,19 +169,3 @@ class Ticket(models.Model): """ return self.edit_timestamp if self.is_edited else self.create_timestamp - - def serialize(self) -> dict: - return { - "id": self.id, - "title": self.title, - "description": self.description, - "author": self.author.serialize(), - "create_timestamp": self.create_timestamp, - "edit_timestamp": self.edit_timestamp, - "is_edited": self.is_edited, - "was_yesterday": self.was_yesterday, - "is_older_than_day": self.is_older_than_day, - "timestamp": self.timestamp, - "priority": self.priority.serialize(), - "tags": [tag.serialize() for tag in self.tags.all()] - } diff --git a/apps/home/urls.py b/apps/home/urls.py index 11f21dc..3c7f478 100644 --- a/apps/home/urls.py +++ b/apps/home/urls.py @@ -1,7 +1,9 @@ # -*- encoding: utf-8 -*- from django.urls import path, re_path, include + from apps.home import views +from .views import TicketView urlpatterns = [ @@ -11,7 +13,7 @@ urlpatterns = [ # Custom Dashboard path('dashboard/', views.dashboard, name="dashboard"), path('tickets/', include([ - path('', views.tickets, name="tickets"), + path('', TicketView.as_view(), name="tickets"), path('new/', views.new_ticket, name="ticket-new"), path('get/', include([ path('one/', views.get_ticket, name="ticket-getone"), @@ -20,7 +22,7 @@ urlpatterns = [ ])), ])), - # Matches any html file - re_path(r'^.*\.*', views.pages, name='pages'), + # # Matches any html file + # re_path(r'^.*\.*', views.pages, name='pages'), ] diff --git a/apps/home/views.py b/apps/home/views.py index 5df9a18..4a459ab 100644 --- a/apps/home/views.py +++ b/apps/home/views.py @@ -11,7 +11,10 @@ from django.template import loader from django.shortcuts import render from django.urls import reverse from django.contrib.auth import get_user_model +from django.views.generic import TemplateView +from django.utils.decorators import method_decorator +from apps.api.serializers import TicketSerializer from ..authentication.models import Department from .models import Ticket, TicketPriority, TicketTag @@ -21,6 +24,111 @@ def dashboard(request): return render(request, "home/dashboard.html") +class TicketView(TemplateView): + template_name = "home/tickets.html" + + @method_decorator(login_required) + def get(self, request): + tickets = Ticket.objects.all().order_by("-create_timestamp") + priorities = TicketPriority.objects.all() + tags = TicketTag.objects.all() + departments = Department.objects.all() + + context = { + "tickets": tickets, + "priorities": priorities, + "tags": tags, + "departments": departments, + "dayago": datetime.now() - timedelta(hours=24) + } + + return render(request, "home/tickets.html", context) + + # @method_decorator(login_required) + # @require_POST + # def fetch_ticket(self, request) -> JsonResponse: + # ticket = Ticket.objects.get(id=request.POST.get("ticket_id")) + # context = {"ticket": ticket.serialize()} + # return JsonResponse(context) + + # @method_decorator(login_required) + # @require_POST + # def fetch_tickets(self, request) -> JsonResponse: + # filters = json.loads(request.POST.get("filters", "{}")) + # queryset = Ticket.objects.all() + + # for key, values in filters.items(): + # print(key, values) + + # for value in values: + # if value == "all": continue # don't apply a filter if we want all + # queryset = queryset.filter(**{key: [value]}) + + # tickets = queryset.order_by("-create_timestamp") + + # context = {"tickets": [ticket.serialize() for ticket in tickets]} + + # return JsonResponse(context) + + # @method_decorator(login_required) + # @require_POST + # def fetch_filter_counts(self, request) -> JsonResponse: + # priorities = TicketPriority.objects.all() + # tags = TicketTag.objects.all() + # departments = Department.objects.all() + + # tickets = Ticket.objects.all() + + # context = { + # "priority_counts": {}, + # "tag_counts": {}, + # "department_counts": {}, + # "ticket_count": tickets.count() + # } + + # for priority in priorities: + # priority_count = tickets.filter(priority=priority).count() + # context["priority_counts"][str(priority.id)] = priority_count + + # for tag in tags: + # tag_count = tickets.filter(tags__in=[tag]).count() + # context["tag_counts"][str(tag.id)] = tag_count + + # for department in departments: + # department_count = tickets.filter(author__department=department).count() + # context["department_counts"][str(department.id)] = department_count + + # return JsonResponse(context) + + # @method_decorator(login_required) + # @require_POST + # def new_ticket(self, request) -> JsonResponse: + + # getall = lambda *keys: [request.POST.get(key) for key in keys] + # getlist = lambda key: request.POST.getlist(key) + + # title, description, author_id, priority_id = getall( + # "title", "description", "author_id", "priority_id" + # ) + # tag_ids = getlist("tag_ids[]") + + # author = get_user_model().objects.get(id=author_id) + # priority = TicketPriority.objects.get(id=priority_id) + # tags = [ + # tag for tag in TicketTag.objects.filter(id__in=tag_ids) + # ] + + # ticket = Ticket.objects.create( + # title=title, + # description=description, + # author=author, + # priority=priority, + # ) + # ticket.tags.set(tags) + + # return JsonResponse({"success": "ticket created successfully"}) + + @login_required() def tickets(request): tickets = Ticket.objects.all().order_by("-create_timestamp") @@ -42,8 +150,9 @@ def tickets(request): @login_required @require_POST def get_ticket(request): - ticket = Ticket.objects.get(id=request.POST.get("ticket_id")) - data = {"ticket": ticket.serialize()} + ticket = Ticket.objects.get(uuid=request.POST.get("ticket_uuid")) + serializer = TicketSerializer(ticket) + data = {"ticket": serializer.data} return JsonResponse(data) @@ -62,8 +171,9 @@ def get_tickets(request): queryset = queryset.filter(**{key: [value]}) tickets = queryset.order_by("-create_timestamp") + serializer = TicketSerializer(tickets, many=True) - data = {"tickets": [ticket.serialize() for ticket in tickets]} + data = {"tickets": serializer.data} return JsonResponse(data) @@ -86,13 +196,13 @@ def get_filter_counts(request): } for priority in priorities: - data["priority_counts"][str(priority.id)] = tickets.filter(priority=priority).count() + data["priority_counts"][str(priority.uuid)] = tickets.filter(priority=priority).count() for tag in tags: - data["tag_counts"][str(tag.id)] = tickets.filter(tags__in=[tag]).count() + data["tag_counts"][str(tag.uuid)] = tickets.filter(tags__in=[tag]).count() for department in departments: - data["department_counts"][str(department.id)] = tickets.filter(author__department=department).count() + data["department_counts"][str(department.uuid)] = tickets.filter(author__department=department).count() return JsonResponse(data) @@ -154,7 +264,6 @@ def pages(request): return HttpResponse(html_template.render(context, request)) except template.TemplateDoesNotExist: - html_template = loader.get_template('home/page-404.html') return HttpResponse(html_template.render(context, request)) diff --git a/apps/templates/home/tickets.html b/apps/templates/home/tickets.html index e874638..d9a04bb 100644 --- a/apps/templates/home/tickets.html +++ b/apps/templates/home/tickets.html @@ -46,10 +46,10 @@ {% for priority in priorities %}