From ff4c71dc54a9815bc54c82b7c7752dada523a498 Mon Sep 17 00:00:00 2001 From: corbz Date: Fri, 5 Jan 2024 00:12:13 +0000 Subject: [PATCH] working on ticket js --- .../migrations/0004_alter_user_managers.py | 18 +++++ apps/authentication/models.py | 41 +++++++++- apps/home/migrations/0002_alter_ticket_id.py | 19 +++++ apps/home/models.py | 28 +++++++ apps/home/urls.py | 4 +- apps/home/views.py | 7 +- apps/templates/home/tickets.html | 81 +++++++++++++------ requirements.txt | 3 + 8 files changed, 168 insertions(+), 33 deletions(-) create mode 100644 apps/authentication/migrations/0004_alter_user_managers.py create mode 100644 apps/home/migrations/0002_alter_ticket_id.py diff --git a/apps/authentication/migrations/0004_alter_user_managers.py b/apps/authentication/migrations/0004_alter_user_managers.py new file mode 100644 index 0000000..283e41d --- /dev/null +++ b/apps/authentication/migrations/0004_alter_user_managers.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.16 on 2024-01-04 22:15 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('authentication', '0003_auto_20240104_1447'), + ] + + operations = [ + migrations.AlterModelManagers( + name='user', + managers=[ + ], + ), + ] diff --git a/apps/authentication/models.py b/apps/authentication/models.py index c479520..46190b8 100644 --- a/apps/authentication/models.py +++ b/apps/authentication/models.py @@ -3,11 +3,34 @@ import uuid from django.db import models -from django.contrib.auth.models import AbstractUser +from django.contrib.auth.models import AbstractUser, BaseUserManager -# default user profile image -# https://st3.depositphotos.com/9998432/13335/v/450/depositphotos_133352156-stock-illustration-default-placeholder-profile-icon.jpg +class UserManager(BaseUserManager): + + def create_user(self, email: str, name: str, password: str=None, **extra_fields): + if not email: + raise ValueError("Please provide an email address") + + email = self.normalize_email(email) + user = self.model(email=email, name=name, **extra_fields) + user.set_password(password) + user.save() + + return user + + def create_superuser(self, email: str, name: str, password: str, **extra_fields): + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + extra_fields.setdefault("is_active", True) + + if not extra_fields.get("is_staff"): + raise ValueError("Superuser must have is_staff=True") + + if not extra_fields.get("is_superuser"): + raise ValueError("Superuser must have is_superuser=True") + + return self.create_user(email, name, password, **extra_fields) class User(AbstractUser): @@ -17,7 +40,17 @@ class User(AbstractUser): email = models.EmailField(unique=True) USERNAME_FIELD = "email" - REQUIRED_FIELDS = ["id", "icon", "name"] + REQUIRED_FIELDS = ["name"] + + objects = UserManager() def __str__(self): return self.name + + def serialize(self) -> dict: + return { + "id": self.id, + "icon": self.icon.url, + "name": self.name, + "email": self.email + } diff --git a/apps/home/migrations/0002_alter_ticket_id.py b/apps/home/migrations/0002_alter_ticket_id.py new file mode 100644 index 0000000..c04b358 --- /dev/null +++ b/apps/home/migrations/0002_alter_ticket_id.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.16 on 2024-01-04 23:51 + +from django.db import migrations, models +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('home', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='ticket', + name='id', + field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), + ), + ] diff --git a/apps/home/models.py b/apps/home/models.py index c8d37a7..40b5399 100644 --- a/apps/home/models.py +++ b/apps/home/models.py @@ -1,5 +1,7 @@ # -*- encoding: utf-8 -*- +import uuid +import bleach from datetime import timedelta, datetime from django.db import models @@ -18,6 +20,7 @@ class TicketTag(models.Model): class Ticket(models.Model): + id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) title = models.CharField(max_length=60) description = models.TextField(max_length=650) author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) @@ -27,6 +30,18 @@ class Ticket(models.Model): def __str__(self): return f"#{self.id} • {self.title} • {self.author}" + def clean_description(self): + cleaned_description = bleach.clean( + self.description, + tags=['b', 'i', 'u', 'p', 'br', 'a', 'h3', 'h4', 'h5', 'h6'], + attributes={'a': ['href', 'title']} + ) + return cleaned_description + + def save(self, *args, **kwargs): + self.description = self.clean_description() + super().save(*args, **kwargs) + @property def is_edited(self) -> bool: """Returns boolean if the ticket is believed to have been edited. @@ -65,3 +80,16 @@ 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, + "is_older_than_day": self.is_older_than_day, + "timestamp": self.timestamp + } diff --git a/apps/home/urls.py b/apps/home/urls.py index e050667..d364fe6 100644 --- a/apps/home/urls.py +++ b/apps/home/urls.py @@ -1,7 +1,4 @@ # -*- encoding: utf-8 -*- -""" -Copyright (c) 2019 - present AppSeed.us -""" from django.urls import path, re_path, include from apps.home import views @@ -16,6 +13,7 @@ urlpatterns = [ path('tickets/', include([ path('', views.tickets, name="tickets"), path('new/', views.new_ticket, name="ticket-new"), + path('get/', views.get_ticket, name="ticket-get"), ])), # Matches any html file diff --git a/apps/home/views.py b/apps/home/views.py index 9d971db..622278a 100644 --- a/apps/home/views.py +++ b/apps/home/views.py @@ -4,10 +4,12 @@ from datetime import timedelta, datetime from django import template from django.contrib.auth.decorators import login_required -from django.http import HttpResponse, HttpResponseRedirect +from django.views.decorators.http import require_POST +from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.template import loader from django.shortcuts import render from django.urls import reverse +from django.forms.models import model_to_dict from .models import Ticket @@ -28,8 +30,11 @@ def tickets(request): return render(request, "home/tickets.html", context) +@require_POST def get_ticket(request, ticket_id: int): ticket = Ticket.objects.get(id=ticket_id) + data = {"ticket": ticket.serialize()} + return JsonResponse(data) diff --git a/apps/templates/home/tickets.html b/apps/templates/home/tickets.html index ee649fc..025f51b 100644 --- a/apps/templates/home/tickets.html +++ b/apps/templates/home/tickets.html @@ -214,7 +214,7 @@
{% if ticket.is_older_than_day %} - {{ ticket.timestamp|date:"w M, Y" }} + {{ ticket.timestamp|date:"D, w M Y" }} {% else %} {{ ticket.timestamp|date:"H:i" }} {% endif %} @@ -225,7 +225,7 @@
{{ ticket.title }}
- {{ ticket.description }} + {{ ticket.description|safe }} {% endfor %} @@ -386,37 +386,68 @@ $('.email-content').toggleClass('open'); displayTicket(ticketElement); }); + + $("#ticketTitle").text("") + $("#ticketDesc").empty(); + $("#ticketAuthor").text(""); + $("#ticketAuthorImg").hide(); + $("#ticketAuthorImg").prop("src", ""); + $("#ticketTimestamp").text(""); + $("#btnGroupDrop2").hide(); + $("#ticketBadges").hide(); if (displayedTicketID === ticketID) { - $("#ticketTitle").text("") - $("#ticketDesc").text(""); - $("#ticketAuthor").text(""); - $("#ticketAuthorImg").hide(); - $("#ticketAuthorImg").prop("src", ""); - $("#ticketTimestamp").text(""); - $("#btnGroupDrop2").hide(); - $("#ticketBadges").hide(); - displayedTicketID = -1; return; } - + displayedTicketID = ticketID; - title = ticket.find(".ticket-title").text(); - desc = ticket.find(".ticket-desc").text(); - author = ticket.find(".ticket-author").text(); - authorIcon = ticket.data("author-icon"); - timestamp = ticket.find(".ticket-timestamp").text(); + $.ajax({ + url: `/tickets/get/${ticketID}`, + type: 'POST', + dataType: 'json', + data: { + csrfmiddlewaretoken: '{{ csrf_token }}' + }, + success: function (data) { + alert(JSON.stringify(data, null, 4)); - $("#ticketTitle").text(title) - $("#ticketDesc").text(desc); - $("#ticketAuthor").text(author); - $("#ticketAuthorImg").show(); - $("#ticketAuthorImg").prop("src", authorIcon); - $("#ticketTimestamp").text(timestamp); - $("#btnGroupDrop2").show(); - $("#ticketBadges").show(); + var ticket = data.ticket; + var author = ticket.author; + + $("#ticketTitle").text(ticket.title); + $("#ticketDesc").append($(ticket.description)); + $("#ticketAuthor").text(author.name); + $("#ticketAuthorImg").show(); + $("#ticketAuthorImg").prop("src", author.icon); + $("#btnGroupDrop2").show(); + $("#ticketBadges").show(); + + // timestamp + var timestamp = new Date(ticket.timestamp); + var formattedTime; + + if (ticket.is_older_than_day) { + var options = { weekday: 'short', day: 'numeric', month: 'short', year: 'numeric' }; + formattedTime = timestamp.toLocaleDateString('en-GB', options); + } + else { + var hours = timestamp.getUTCHours(); + var minutes = timestamp.getUTCMinutes(); + formattedTime = hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0'); + } + + if (ticket.is_edited) { + formattedTime += " • edited"; + } + + $("#ticketTimestamp").text(formattedTime); + }, + error: function(message) { + alert(JSON.stringify(message, null, 4)); + } + }); } {% endblock javascripts %} diff --git a/requirements.txt b/requirements.txt index 654b59e..101525f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ asgiref==3.4.1 autopep8==1.6.0 +bleach==6.1.0 dj-database-url==0.5.0 Django==3.2.16 django-environ==0.8.1 @@ -7,6 +8,8 @@ gunicorn==20.1.0 pillow==10.2.0 pycodestyle==2.8.0 pytz==2021.3 +six==1.16.0 sqlparse==0.4.2 toml==0.10.2 +webencodings==0.5.1 whitenoise==5.3.0