Merge pull request 'jones-dev' (#19) from jones-dev into master

Reviewed-on: https://gitea.corbz.dev/corbz/ticket-website/pulls/19
This commit is contained in:
Corban-Lee Jones 2024-01-26 17:39:03 +00:00
commit 709c19f6e8
103 changed files with 96574 additions and 78482 deletions

18
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,18 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Django",
"type": "python",
"request": "launch",
"python": "${workspaceFolder}/venv/bin/python",
"program": "${workspaceFolder}/manage.py",
"args": ["runserver"],
"django": true,
"justMyCode": true
}
]
}

View File

@ -48,6 +48,7 @@ The following fixture options are available:
- `authentication/department.json` add some default departments
- `home/ticketpriority.json` add some default ticket priorities
- `home/tickettag.json` add some default ticket tags
- `home/defaulttickets.json` add some default tickets (depends on ticketpriority and tickettag fixtures)
You can import a fixture using the following command (remember to replace `<OPTION>` with one of the above options):

3
apps/api/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

8
apps/api/config.py Normal file
View File

@ -0,0 +1,8 @@
# -*- encoding: utf-8 -*-
from django.apps import AppConfig
class MyConfig(AppConfig):
name = 'apps.api'
label = 'apps_api'

View File

3
apps/api/models.py Normal file
View File

@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

156
apps/api/serializers.py Normal file
View File

@ -0,0 +1,156 @@
# -*- encoding: utf-8 -*-
import logging
from django.apps import apps
from django.conf import settings
from rest_framework import serializers
from apps.home.models import Ticket, TicketPriority, TicketTag
from apps.authentication.models import Department
log = logging.getLogger(__name__)
class DynamicModelSerializer(serializers.ModelSerializer):
"""For use with GET requests, to specify which fields to include or exclude
Mimics some graphql functionality.
Usage: Inherit your ModelSerializer with this class. Add "only_fields" or
"exclude_fields" to the query parameters of your GET request.
This also works with nested foreign keys, for example:
?only_fields=name,age&company__only_fields=id,name
Some more examples:
?only_fields=company,name&company__exclude_fields=name
?exclude_fields=name&company__only_fields=id
?company__exclude_fields=name
Note: the Foreign Key serializer must also inherit from this class
"""
def only_keep_fields(self, fields_to_keep):
fields_to_keep = set(fields_to_keep.split(","))
all_fields = set(self.fields.keys())
for field in all_fields - fields_to_keep:
self.fields.pop(field, None)
def exclude_fields(self, fields_to_exclude):
fields_to_exclude = fields_to_exclude.split(",")
for field in fields_to_exclude:
self.fields.pop(field, None)
def remove_unwanted_fields(self, dynamic_params):
if fields_to_keep := dynamic_params.pop("only_fields", None):
self.only_keep_fields(fields_to_keep)
if fields_to_exclude := dynamic_params.pop("exclude_fields", None):
self.exclude_fields(fields_to_exclude)
def get_or_create_dynamic_params(self, child):
if "dynamic_params" not in self.fields[child]._context:
self.fields[child]._context.update({"dynamic_params": {}})
return self.fields[child]._context["dynamic_params"]
@staticmethod
def split_param(dynamic_param):
crumbs = dynamic_param.split("__")
return crumbs[0], "__".join(crumbs[1:]) if len(crumbs) > 1 else None
def set_dynamic_params_for_children(self, dynamic_params):
for param, fields in dynamic_params.items():
child, child_dynamic_param = self.split_param(param)
if child in set(self.fields.keys()):
dynamic_params = self.get_or_create_dynamic_params(child)
dynamic_params.update({child_dynamic_param: fields})
@staticmethod
def is_param_dynamic(p):
return p.endswith("only_fields") or p.endswith("exclude_fields")
def get_dynamic_params_for_root(self, request):
query_params = request.query_params.items()
return {k: v for k, v in query_params if self.is_param_dynamic(k)}
def get_dynamic_params(self):
"""
When dynamic params get passed down in set_context_for_children
If the child is a subclass of ListSerializer (has many=True)
The context must be fetched from ListSerializer Class
"""
if isinstance(self.parent, serializers.ListSerializer):
return self.parent._context.get("dynamic_params", {})
return self._context.get("dynamic_params", {})
def __init__(self, *args, **kwargs):
request = kwargs.get("context", {}).get("request")
super().__init__(*args, **kwargs)
is_root = bool(request)
if is_root:
if request.method != "GET":
return
dynamic_params = self.get_dynamic_params_for_root(request)
self._context.update({"dynamic_params": dynamic_params})
def to_representation(self, *args, **kwargs):
if dynamic_params := self.get_dynamic_params().copy():
self.remove_unwanted_fields(dynamic_params)
self.set_dynamic_params_for_children(dynamic_params)
return super().to_representation(*args, **kwargs)
class Meta:
abstract = True
class DepartmentSerializer(DynamicModelSerializer):
class Meta:
model = Department
fields = [
"uuid", "title", "colour", "backgroundcolour", "order"
]
class UserSerializer(DynamicModelSerializer):
department = DepartmentSerializer()
class Meta:
model = apps.get_model(settings.AUTH_USER_MODEL)
fields = [
"uuid", "icon", "email", "forename", "surname", "department",
"create_timestamp", "edit_timestamp"
]
class TicketPrioritySerializer(DynamicModelSerializer):
class Meta:
model = TicketPriority
fields = [
"uuid", "title", "colour", "backgroundcolour", "order"
]
class TicketTagSerializer(DynamicModelSerializer):
class Meta:
model = TicketTag
fields = [
"uuid", "title", "colour", "backgroundcolour", "order"
]
class TicketSerializer(DynamicModelSerializer):
author = UserSerializer()
priority = TicketPrioritySerializer()
tags = TicketTagSerializer(many=True)
class Meta:
model = Ticket
fields = (
"uuid", "title", "description", "author", "create_timestamp",
"edit_timestamp", "is_edited", "timestamp", "priority", "tags",
"short_description", "display_datetime"
)

3
apps/api/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

15
apps/api/urls.py Normal file
View File

@ -0,0 +1,15 @@
# -*- encoding: utf-8 -*-
from django.urls import path, include
from rest_framework.authtoken.views import obtain_auth_token
from . import views
urlpatterns = [
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
path("api-token-auth/", obtain_auth_token),
path("tickets/", views.TicketListApiView.as_view(), name="tickets"),
path("filter-counts/", views.FilterCountListApiView.as_view(), name="filter-counts"),
]

94
apps/api/views.py Normal file
View File

@ -0,0 +1,94 @@
# -*- encoding: utf-8 -*-
import logging
from django_filters import rest_framework as rest_filters
from django.views.decorators.cache import cache_page
from django.utils.decorators import method_decorator
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status, permissions, filters, generics
from rest_framework.pagination import PageNumberPagination
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
from apps.home.models import Ticket, TicketPriority, TicketTag
from apps.authentication.models import Department
from .serializers import (
TicketSerializer, TicketTagSerializer, TicketPrioritySerializer,
UserSerializer
)
log = logging.getLogger(__name__)
class TicketPaginiation(PageNumberPagination):
page_size = 25
page_size_query_param = "page_size"
max_page_size = 100
class TicketListApiView(generics.ListAPIView):
"""Returns a list of all **Tickets** on the system.
Use a querystring to filter down, order, and search the results.
Querystring options are:
- uuid
- priority
- tags
- author
- author__department
"""
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
pagination_class = TicketPaginiation
serializer_class = TicketSerializer
queryset = Ticket.objects.all()
filter_backends = [filters.SearchFilter, rest_filters.DjangoFilterBackend, filters.OrderingFilter]
filterset_fields = ["uuid", "priority", "tags", "author", "author__department"]
search_fields = ["author__forename", "author__surname", "title", "description"]
ordering_fields = ["create_timestamp", "edit_timestamp"]
def get_queryset(self):
strict_tags = self.request.query_params.get("strict-tags")
if not strict_tags:
return self.queryset
tag_uuids = self.request.query_params.getlist("tags", [])
queryset = self.queryset
log.debug("tag uuids %s", tag_uuids)
for uuid in tag_uuids:
if not uuid: continue
log.debug("uuid %s", uuid)
queryset = queryset.filter(tags__uuid__in=[uuid])
return queryset
class FilterCountListApiView(generics.ListAPIView):
authentication_classes = [SessionAuthentication, TokenAuthentication]
permission_classes = [permissions.IsAuthenticated]
def get(self, request):
self._tickets = Ticket.objects.all()
data = {"tickets": self._tickets.count()}
self._fill_data(TicketPriority, data, "priority")
self._fill_data(TicketTag, data, "tags")
self._fill_data(Department, data, "department", model_key="author__department")
return Response(data, status.HTTP_200_OK)
def _fill_data(self, model, data: dict, key: str, *, model_key:str=None):
data[key] = {} # prevent KeyError
objects = model.objects.all()
for obj in objects:
data[key][str(obj.uuid)] = self._tickets.filter(**{model_key or key: obj}).count()

View File

@ -4,5 +4,26 @@ from django.contrib import admin
from .models import User, Department
admin.site.register(User)
admin.site.register(Department)
class UserAdmin(admin.ModelAdmin):
list_display = ["uuid", "forename", "surname", "email", "get_department_title", "icon", "is_staff", "is_superuser", "is_active"]
list_filter = ["department__title", "is_staff", "is_superuser", "is_active"]
ordering = ["department__title"]
@admin.display(description="Department", ordering="department__title")
def get_department_title(self, obj):
return obj.department.title if obj.department else None
class DepartmentAdmin(admin.ModelAdmin):
list_display = ["uuid", "title", "colour", "backgroundcolour", "order", "get_user_count"]
@admin.display(description="Users")
def get_user_count(self, obj):
return User.objects.filter(department=obj).count()
admin.site.register(User, UserAdmin)
admin.site.register(Department, DepartmentAdmin)

View File

@ -4,7 +4,9 @@
"pk": "4e245769-6b67-4a6e-b804-54a3ceb3b8c0",
"fields": {
"title": "Development",
"icon": null
"colour": "#1976d2",
"backgroundcolour": "#bbdefb",
"order": 4
}
},
{
@ -12,7 +14,9 @@
"pk": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"fields": {
"title": "Marketing",
"icon": null
"colour": "#7b1fa2",
"backgroundcolour": "#e1bee7",
"order": 1
}
},
{
@ -20,7 +24,9 @@
"pk": "a6517555-0bcc-4baa-8e2f-798916562b1c",
"fields": {
"title": "Management",
"icon": null
"colour": "#689f38",
"backgroundcolour": "#dcedc8",
"order": 0
}
},
{
@ -28,7 +34,9 @@
"pk": "bae35c7a-a929-4465-b70f-03254b0774e0",
"fields": {
"title": "Sales",
"icon": null
"colour": "#e64a19",
"backgroundcolour": "#ffccbc",
"order": 2
}
},
{
@ -36,7 +44,9 @@
"pk": "c2bbabd7-05ac-4bc8-97a3-15bafdb478d9",
"fields": {
"title": "Business Strategy",
"icon": null
"colour": "#c2185b",
"backgroundcolour": "#f8bbd0",
"order": 3
}
}
]

View File

@ -0,0 +1,699 @@
[
{
"model": "authentication.user",
"pk": "06a8d977-d67d-432c-96d4-6e41993f6423",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/06a8d977-d67d-432c-96d4-6e41993f6423/icon.webp",
"email": "andy.horton@example.com",
"forename": "Andy",
"surname": "Horton",
"department": "bae35c7a-a929-4465-b70f-03254b0774e0",
"create_timestamp": "2024-01-13T21:01:27Z",
"edit_timestamp": "2024-01-13T21:02:12.965Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "07b620ee-6b47-4bca-842a-0fd6d5204eb8",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/07b620ee-6b47-4bca-842a-0fd6d5204eb8/icon.webp",
"email": "luke.wright@example.com",
"forename": "Luke",
"surname": "Wright",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T20:54:15Z",
"edit_timestamp": "2024-01-13T20:55:15.013Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "0b2cd1c5-cb96-4b23-a1cd-0a306b5894a1",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/0b2cd1c5-cb96-4b23-a1cd-0a306b5894a1/icon.webp",
"email": "carl.brooks@example.com",
"forename": "Carl",
"surname": "Brooks",
"department": "bae35c7a-a929-4465-b70f-03254b0774e0",
"create_timestamp": "2024-01-13T21:00:16Z",
"edit_timestamp": "2024-01-13T21:00:52.168Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "0b437298-7c00-47fa-a54f-d3cfdf39a0c3",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/0b437298-7c00-47fa-a54f-d3cfdf39a0c3/icon.webp",
"email": "vera.jones@example.com",
"forename": "Vera",
"surname": "Jones",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T20:39:37Z",
"edit_timestamp": "2024-01-13T20:40:30.134Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "1255e2cd-1876-452f-ab1a-bd893dcfbfff",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/1255e2cd-1876-452f-ab1a-bd893dcfbfff/icon.webp",
"email": "chris.rodriquez@example.com",
"forename": "Chris",
"surname": "Rodriquez",
"department": "c2bbabd7-05ac-4bc8-97a3-15bafdb478d9",
"create_timestamp": "2024-01-13T20:34:57Z",
"edit_timestamp": "2024-01-13T20:35:34.086Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "21b457a1-b64a-4499-8c53-0e2f3b42fe3c",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/21b457a1-b64a-4499-8c53-0e2f3b42fe3c/icon.webp",
"email": "gilbert.baker@example.com",
"forename": "Gilbert",
"surname": "Baker",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T19:59:56Z",
"edit_timestamp": "2024-01-13T20:00:58.570Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "248bc1ef-df52-445e-847c-e370dccf436a",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/248bc1ef-df52-445e-847c-e370dccf436a/icon.webp",
"email": "noah.thompson@example.com",
"forename": "Noah",
"surname": "Thompson",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T20:38:40Z",
"edit_timestamp": "2024-01-13T20:39:32.879Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "2955ba6a-bffb-48a7-9e61-b2b87ac27f3d",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/2955ba6a-bffb-48a7-9e61-b2b87ac27f3d/icon.webp",
"email": "amelia.kelley@example.com",
"forename": "Amelia",
"surname": "Kelley",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T20:56:45Z",
"edit_timestamp": "2024-01-13T20:57:23.453Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "420f5146-ed03-495a-982a-6dabefd7fd62",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/420f5146-ed03-495a-982a-6dabefd7fd62/icon.webp",
"email": "gavin.murphy@example.com",
"forename": "Gavin",
"surname": "Murphy",
"department": "4e245769-6b67-4a6e-b804-54a3ceb3b8c0",
"create_timestamp": "2024-01-13T20:30:56Z",
"edit_timestamp": "2024-01-13T20:31:45.569Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "48ec50cd-d89b-41d7-bcb0-92612f0cf104",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/48ec50cd-d89b-41d7-bcb0-92612f0cf104/icon.webp",
"email": "alexander.butler@example.com",
"forename": "Alexander",
"surname": "Butler",
"department": "4e245769-6b67-4a6e-b804-54a3ceb3b8c0",
"create_timestamp": "2024-01-13T20:37:16Z",
"edit_timestamp": "2024-01-13T20:38:00.511Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "4965745e-b82a-4496-80e1-055217a780b0",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": "2024-01-13T20:53:38.159Z",
"email": "admin@mail.com",
"forename": "Default",
"surname": "Admin User",
"department": null,
"create_timestamp": "2024-01-08T23:14:13.402Z",
"edit_timestamp": "2024-01-08T23:14:13.444Z",
"is_active": true,
"is_staff": true,
"is_superuser": true,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "4b203599-8de8-45de-b9aa-8ad88230ac2f",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/4b203599-8de8-45de-b9aa-8ad88230ac2f/icon.webp",
"email": "grace.chapman@example.com",
"forename": "Grace",
"surname": "Chapman",
"department": "c2bbabd7-05ac-4bc8-97a3-15bafdb478d9",
"create_timestamp": "2024-01-13T20:40:30Z",
"edit_timestamp": "2024-01-13T21:04:56.382Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "639f6399-60a9-4597-b9d4-d1df6680bb4a",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/639f6399-60a9-4597-b9d4-d1df6680bb4a/icon.webp",
"email": "tomothy.mitchelle@example.com",
"forename": "Tomothy",
"surname": "Mitchelle",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T19:59:17Z",
"edit_timestamp": "2024-01-13T19:59:56.374Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "64bbc7c0-6c08-4307-9740-419b0b279d2e",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/64bbc7c0-6c08-4307-9740-419b0b279d2e/icon.webp",
"email": "jerome.larson@example.com",
"forename": "Jerome",
"surname": "Larson",
"department": "4e245769-6b67-4a6e-b804-54a3ceb3b8c0",
"create_timestamp": "2024-01-13T20:33:13Z",
"edit_timestamp": "2024-01-13T20:33:54.560Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "6e0af77b-36f7-41a5-93ab-dfd6663b80d5",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/6e0af77b-36f7-41a5-93ab-dfd6663b80d5/icon.webp",
"email": "philip.martinez@example.com",
"forename": "Philip",
"surname": "Martinez",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T20:36:46Z",
"edit_timestamp": "2024-01-13T20:37:16.023Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "77b65a5b-2399-47cd-a34c-62454309a51a",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/77b65a5b-2399-47cd-a34c-62454309a51a/icon.webp",
"email": "clyde.pena@example.com",
"forename": "Clyde",
"surname": "Pena",
"department": "bae35c7a-a929-4465-b70f-03254b0774e0",
"create_timestamp": "2024-01-13T20:41:24Z",
"edit_timestamp": "2024-01-13T20:42:05.979Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "79ab62dc-878e-4563-8b82-1537de6f4a3c",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/79ab62dc-878e-4563-8b82-1537de6f4a3c/icon.webp",
"email": "brianna.rice@example.com",
"forename": "Brianna",
"surname": "Rice",
"department": "a6517555-0bcc-4baa-8e2f-798916562b1c",
"create_timestamp": "2024-01-13T20:36:07Z",
"edit_timestamp": "2024-01-13T20:36:46.505Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "811ad22b-5153-421f-bb84-6addcb8de570",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/811ad22b-5153-421f-bb84-6addcb8de570/icon.webp",
"email": "cameron.lowe@example.com",
"forename": "Cameron",
"surname": "Lowe",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T20:57:23Z",
"edit_timestamp": "2024-01-13T20:58:45.754Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "855d09bb-5919-4dff-8652-50aa1fb81b54",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/855d09bb-5919-4dff-8652-50aa1fb81b54/icon.webp",
"email": "jerry.porter@example.com",
"forename": "Jerry",
"surname": "Porter",
"department": "a6517555-0bcc-4baa-8e2f-798916562b1c",
"create_timestamp": "2024-01-13T20:29:04Z",
"edit_timestamp": "2024-01-13T20:29:49.442Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "8b504054-ab46-4866-8a7e-46aaab29e54b",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": "2024-01-13T19:58:12Z",
"email": "ricky.simmmons@example.com",
"forename": "Ricky",
"surname": "Simmmons",
"department": "c2bbabd7-05ac-4bc8-97a3-15bafdb478d9",
"create_timestamp": "2024-01-13T19:57:37Z",
"edit_timestamp": "2024-01-13T19:58:59.815Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "94ca520d-4f5a-49c6-ab2b-9fbb362c51d9",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/94ca520d-4f5a-49c6-ab2b-9fbb362c51d9/icon.webp",
"email": "arthur.flores@example.com",
"forename": "Arthur",
"surname": "Flores",
"department": "a6517555-0bcc-4baa-8e2f-798916562b1c",
"create_timestamp": "2024-01-13T20:38:00Z",
"edit_timestamp": "2024-01-13T20:38:40.002Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "9cc67b83-2ccc-454e-be4a-9c772cb23d87",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/9cc67b83-2ccc-454e-be4a-9c772cb23d87/icon.webp",
"email": "ramon.sullivan@example.com",
"forename": "Ramon",
"surname": "Sullivan",
"department": "bae35c7a-a929-4465-b70f-03254b0774e0",
"create_timestamp": "2024-01-13T20:33:54Z",
"edit_timestamp": "2024-01-13T20:34:57.626Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "9d99a266-7409-4a17-aad9-2696a4d89a7c",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/9d99a266-7409-4a17-aad9-2696a4d89a7c/icon.webp",
"email": "micheal.hart@example.com",
"forename": "Micheal",
"surname": "Hart",
"department": "4e245769-6b67-4a6e-b804-54a3ceb3b8c0",
"create_timestamp": "2024-01-13T20:51:53Z",
"edit_timestamp": "2024-01-13T20:52:21.016Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "9f469a37-4d8d-4bd0-ba4e-16b07549f42a",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/9f469a37-4d8d-4bd0-ba4e-16b07549f42a/icon.webp",
"email": "christopher.steward@example.com",
"forename": "Christopher",
"surname": "Steward",
"department": "bae35c7a-a929-4465-b70f-03254b0774e0",
"create_timestamp": "2024-01-13T20:58:45Z",
"edit_timestamp": "2024-01-13T20:59:24.489Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "a0d87275-5400-4476-8301-2d1f51808269",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": "2024-01-13T19:55:36Z",
"icon": "users/a0d87275-5400-4476-8301-2d1f51808269/icon.webp",
"email": "leo.hall@example.com",
"forename": "Leo",
"surname": "Hall",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T19:55:29Z",
"edit_timestamp": "2024-01-13T19:59:17.528Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "ac0ae9dd-e447-428e-9ef1-a9e32a638419",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/ac0ae9dd-e447-428e-9ef1-a9e32a638419/icon.webp",
"email": "claudia.thompson@example.com",
"forename": "Claudia",
"surname": "Thompson",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T20:32:34Z",
"edit_timestamp": "2024-01-13T20:33:13.643Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "b6c7372c-2006-4257-9463-20f63c840403",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/b6c7372c-2006-4257-9463-20f63c840403/icon.webp",
"email": "christopher.sanchez@example.com",
"forename": "Christopher",
"surname": "Sanchez",
"department": "c2bbabd7-05ac-4bc8-97a3-15bafdb478d9",
"create_timestamp": "2024-01-13T20:29:49Z",
"edit_timestamp": "2024-01-13T20:30:56.492Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "be2acfc8-7623-4f5f-864b-4163e07b881d",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"email": "dora.hudson@example.com",
"forename": "Dora",
"surname": "Hudson",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T20:35:34Z",
"edit_timestamp": "2024-01-13T20:36:07.313Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "c1984735-fdca-488b-b4e9-16bd01f3aebb",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/c1984735-fdca-488b-b4e9-16bd01f3aebb/icon.webp",
"email": "mike.james@example.com",
"forename": "Mike",
"surname": "James",
"department": "bae35c7a-a929-4465-b70f-03254b0774e0",
"create_timestamp": "2024-01-13T20:53:42Z",
"edit_timestamp": "2024-01-13T20:54:15.252Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/c53ce609-5775-4ab6-8995-c22c1d96f00b/icon.webp",
"email": "alfredo.fernandez@example.com",
"forename": "Alfredo",
"surname": "Fernandez",
"department": "a6517555-0bcc-4baa-8e2f-798916562b1c",
"create_timestamp": "2024-01-13T20:56:00Z",
"edit_timestamp": "2024-01-13T20:56:45.840Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "d84476db-f240-41ba-af9c-b23eba22894e",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/d84476db-f240-41ba-af9c-b23eba22894e/icon.webp",
"email": "donald.campbell@example.com",
"forename": "Donald",
"surname": "Campbell",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T21:03:17Z",
"edit_timestamp": "2024-01-13T21:03:35.083Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "de7ef0f6-dbe8-49be-864c-19a94465c68a",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/de7ef0f6-dbe8-49be-864c-19a94465c68a/icon.webp",
"email": "daryl.shaw@example.com",
"forename": "Daryl",
"surname": "Shaw",
"department": "a6517555-0bcc-4baa-8e2f-798916562b1c",
"create_timestamp": "2024-01-13T20:31:45Z",
"edit_timestamp": "2024-01-13T20:32:34.293Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "e802e8b4-d232-4b4a-b9c9-3e0e091145e6",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/e802e8b4-d232-4b4a-b9c9-3e0e091145e6/icon.webp",
"email": "larry.fernandez@example.com",
"forename": "Larry",
"surname": "Fernandez",
"department": "c2bbabd7-05ac-4bc8-97a3-15bafdb478d9",
"create_timestamp": "2024-01-13T21:01:07Z",
"edit_timestamp": "2024-01-13T21:01:27.482Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "f1d088f2-f4ae-4fdd-bfea-4bb54d36f3d2",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/f1d088f2-f4ae-4fdd-bfea-4bb54d36f3d2/icon.webp",
"email": "lydia.pearson@example.com",
"forename": "Lydia",
"surname": "Pearson",
"department": "85b46ae8-0a19-48b7-8a21-a01abd78a470",
"create_timestamp": "2024-01-13T20:59:24Z",
"edit_timestamp": "2024-01-13T21:00:16.950Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "fdaa7ec8-64f7-4496-b286-ba0a971d4290",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "users/fdaa7ec8-64f7-4496-b286-ba0a971d4290/icon.webp",
"email": "lena.daniels@example.com",
"forename": "Lena",
"surname": "Daniels",
"department": "a6517555-0bcc-4baa-8e2f-798916562b1c",
"create_timestamp": "2024-01-13T20:55:15Z",
"edit_timestamp": "2024-01-13T20:56:00.068Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
}
]

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.16 on 2024-01-07 18:45
# Generated by Django 3.2.16 on 2024-01-22 14:39
import apps.authentication.models
from django.db import migrations, models
@ -19,9 +19,11 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Department',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('title', models.CharField(max_length=150)),
('icon', models.CharField(blank=True, max_length=32, null=True)),
('colour', models.CharField(max_length=7)),
('backgroundcolour', models.CharField(max_length=7)),
('order', models.PositiveIntegerField(default=0)),
],
),
migrations.CreateModel(
@ -29,8 +31,8 @@ class Migration(migrations.Migration):
fields=[
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('icon', models.ImageField(default='../static/assets/images/defaultuser.webp', storage=apps.authentication.models.OverwriteStorage(), upload_to=apps.authentication.models.IconPathGenerator(), verbose_name='profile picture')),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('icon', models.ImageField(default='../static/images/defaultuser.webp', storage=apps.authentication.models.OverwriteStorage(), upload_to=apps.authentication.models.IconPathGenerator(), verbose_name='profile picture')),
('email', models.EmailField(error_messages={'unique': 'A user with this email address already exists.'}, max_length=254, unique=True, verbose_name='email address')),
('forename', models.CharField(help_text='This should be your real first name.', max_length=150, verbose_name='first name')),
('surname', models.CharField(help_text='This should be your real last name.', max_length=150, verbose_name='last name')),

View File

@ -1,9 +1,10 @@
# -*- encoding: utf-8 -*-
import uuid
from uuid import uuid4
import os
from django.db import models
from django.conf import settings
from django.utils import timezone
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.utils.translation import gettext_lazy as _
@ -23,24 +24,20 @@ 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)
colour = models.CharField(max_length=7)
backgroundcolour = models.CharField(max_length=7)
order = models.PositiveIntegerField(default=0, blank=False, null=False)
def __str__(self):
return self.title
def serialize(self) -> dict:
return {
"title": self.title,
"icon": self.icon
}
class UserManager(BaseUserManager):
@ -70,12 +67,12 @@ 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"),
upload_to=IconPathGenerator(),
default="../static/assets/images/defaultuser.webp",
default="../static/images/defaultuser.webp", # Path starts from media dir
storage=OverwriteStorage()
)
email = models.EmailField(
@ -145,7 +142,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 +159,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
}

View File

@ -1,42 +0,0 @@
[
{
"model": "authentication.user",
"pk": "291cc4c1-3bb3-4417-aaa6-748606fede77",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": null,
"icon": "../static/assets/images/defaultuser.webp",
"email": "user@mail.com",
"forename": "Default",
"surname": "User",
"department": null,
"create_timestamp": "2024-01-08T23:16:17Z",
"edit_timestamp": "2024-01-08T23:17:01.102Z",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"groups": [],
"user_permissions": []
}
},
{
"model": "authentication.user",
"pk": "4965745e-b82a-4496-80e1-055217a780b0",
"fields": {
"password": "pbkdf2_sha256$260000$h4zLYmIeMQgJ0ko41i6dxo$Gwe0TV75ibdJTtqdTOOs5ucOEhA9DUM/bwxjagVhKKg=",
"last_login": "2024-01-08T23:15:16.884Z",
"icon": "../static/assets/images/defaultuser.webp",
"email": "admin@mail.com",
"forename": "Default",
"surname": "Admin User",
"department": null,
"create_timestamp": "2024-01-08T23:14:13.402Z",
"edit_timestamp": "2024-01-08T23:14:13.444Z",
"is_active": true,
"is_staff": true,
"is_superuser": true,
"groups": [],
"user_permissions": []
}
}
]

View File

@ -1,9 +1,47 @@
# -*- encoding: utf-8 -*-
from collections.abc import Callable
from typing import Any
from django.contrib import admin
from .models import Ticket, TicketPriority, TicketTag
admin.site.register(Ticket)
admin.site.register(TicketPriority)
admin.site.register(TicketTag)
class TicketAdmin(admin.ModelAdmin):
list_display = ["uuid", "title", "get_author_name", "get_priority_title", "get_tags_count"]
@admin.display(description="Author", ordering="author__name")
def get_author_name(self, obj):
return obj.author.fullname if obj.author else None
@admin.display(description="Priority", ordering="priority__title")
def get_priority_title(self, obj):
return obj.priority.title if obj.priority else None
@admin.display(description="Tags")
def get_tags_count(self, obj):
return obj.tags.count()
class TicketPriorityAdmin(admin.ModelAdmin):
list_display = ["uuid", "title", "colour", "backgroundcolour", "order", "get_used_count"]
@admin.display(description="Used by")
def get_used_count(self, obj):
return Ticket.objects.filter(priority=obj).count()
class TicketTagAdmin(admin.ModelAdmin):
list_display = ["uuid", "title", "colour", "backgroundcolour", "order", "get_used_count"]
@admin.display(description="Used by")
def get_used_count(self, obj):
return Ticket.objects.filter(tags__in=[obj]).count()
admin.site.register(Ticket, TicketAdmin)
admin.site.register(TicketPriority, TicketPriorityAdmin)
admin.site.register(TicketTag, TicketTagAdmin)

View File

@ -1,7 +1,4 @@
# -*- encoding: utf-8 -*-
"""
Copyright (c) 2019 - present AppSeed.us
"""
from django.apps import AppConfig

View File

@ -0,0 +1,634 @@
[
{
"model": "home.ticket",
"pk": "0725deef-d48c-4e38-814a-ae35fcbad152",
"fields": {
"title": "Software Compatibility Challenge",
"description": "Attempting to integrate a new software tool into our existing workflow, but facing compatibility challenges with other applications. Providing a detailed overview of the software stack and the specific issues encountered during integration attempts. Seeking expert guidance to resolve compatibility issues and ensure smooth workflow integration.",
"author": "21b457a1-b64a-4499-8c53-0e2f3b42fe3c",
"priority": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
"create_timestamp": "2024-01-09T00:11:40Z",
"edit_timestamp": "2024-01-03T15:53:48.787Z",
"tags": [
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
"72fb255c-132f-4124-802d-f4c051620540",
"cc473838-acaf-43f9-a601-dc4ab1f9026c"
]
}
},
{
"model": "home.ticket",
"pk": "1e648e67-ddf6-4007-bb68-4a863961a827",
"fields": {
"title": "Security Patch Installation",
"description": "There's a critical security patch that needs to be installed on all workstations to address recent vulnerabilities. Attempted to deploy the patch, but facing challenges on certain machines. Need high-priority assistance to ensure all systems are promptly updated for enhanced security measures.",
"author": "248bc1ef-df52-445e-847c-e370dccf436a",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"create_timestamp": "2024-01-09T00:09:20Z",
"edit_timestamp": "2023-06-29T15:53:31.535Z",
"tags": [
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
"72fb255c-132f-4124-802d-f4c051620540",
"cc473838-acaf-43f9-a601-dc4ab1f9026c"
]
}
},
{
"model": "home.ticket",
"pk": "433200a2-3a62-4e81-86cc-cd5e31fecd8b",
"fields": {
"title": "Network Connectivity Issue",
"description": "Experiencing intermittent connection drops in the office. Several users affected. Need urgent assistance to resolve the issue.",
"author": "9f469a37-4d8d-4bd0-ba4e-16b07549f42a",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"create_timestamp": "2024-01-09T00:05:54Z",
"edit_timestamp": "2024-01-13T15:53:18.994Z",
"tags": [
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
"28b160b5-5c8b-43a5-84d1-4179bde87e6f",
"72fb255c-132f-4124-802d-f4c051620540"
]
}
},
{
"model": "home.ticket",
"pk": "4b893b70-8fce-49a1-b546-d1960ef97f9c",
"fields": {
"title": "New Software Request",
"description": "Requesting the installation of a new software tool for project management. Providing specific details on the software requirements and its relevance to project workflows. Seeking guidance on the installation process and any potential compatibility issues. Your assistance in this matter is highly appreciated.",
"author": "77b65a5b-2399-47cd-a34c-62454309a51a",
"priority": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
"create_timestamp": "2024-01-09T00:09:59Z",
"edit_timestamp": "2024-01-16T15:52:55.759Z",
"tags": [
"72fb255c-132f-4124-802d-f4c051620540",
"cc473838-acaf-43f9-a601-dc4ab1f9026c",
"dc4dc2d5-2784-4726-8a4e-99df35a143d2"
]
}
},
{
"model": "home.ticket",
"pk": "587b35a3-1b47-4716-99bf-ebe22da283a1",
"fields": {
"title": "Database Query Performance",
"description": "Users are reporting slow response times when querying the database. This issue is impacting productivity across multiple teams. Detailed logs and steps taken to troubleshoot are provided within the description. Urgently seeking assistance to optimize database performance and resolve the slowdown.",
"author": "811ad22b-5153-421f-bb84-6addcb8de570",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"create_timestamp": "2024-01-09T00:08:32Z",
"edit_timestamp": "2024-01-16T15:52:45.715Z",
"tags": [
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
"28b160b5-5c8b-43a5-84d1-4179bde87e6f",
"72fb255c-132f-4124-802d-f4c051620540",
"cc473838-acaf-43f9-a601-dc4ab1f9026c",
"e8c7e801-57c3-4699-a4f4-4308fb489f60"
]
}
},
{
"model": "home.ticket",
"pk": "5f39644f-0356-4818-aa07-ee6f07f36553",
"fields": {
"title": "Hardware Malfunction",
"description": "The printer on the third floor is not responding. Checked cables and power source, but issue persists. Need assistance to fix the hardware problem.",
"author": "f1d088f2-f4ae-4fdd-bfea-4bb54d36f3d2",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"create_timestamp": "2024-01-09T00:06:58Z",
"edit_timestamp": "2024-01-16T15:52:35.868Z",
"tags": [
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
"72fb255c-132f-4124-802d-f4c051620540",
"e8c7e801-57c3-4699-a4f4-4308fb489f60"
]
}
},
{
"model": "home.ticket",
"pk": "68fa33a7-9ac0-4612-8d92-71201c1dd5d5",
"fields": {
"title": "Printer Configuration Error",
"description": "Encountering issues with configuring a new printer for the design team. Detailed steps taken to set up the printer and the specific error messages received are provided. Urgently seeking assistance to ensure the printer is operational and meets the team's printing requirements.",
"author": "0b437298-7c00-47fa-a54f-d3cfdf39a0c3",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"create_timestamp": "2024-01-09T00:10:25Z",
"edit_timestamp": "2024-01-16T15:52:25.424Z",
"tags": [
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
"72fb255c-132f-4124-802d-f4c051620540",
"e8c7e801-57c3-4699-a4f4-4308fb489f60"
]
}
},
{
"model": "home.ticket",
"pk": "9f72bd78-2d1f-4fd6-89fc-d9ecf8c69b3c",
"fields": {
"title": "VPN Connection Issue",
"description": "Remote team members are encountering difficulties establishing a VPN connection. This is hindering their ability to access essential resources. Providing detailed information on the error messages received and troubleshooting steps taken so far. Requesting immediate attention to restore seamless VPN functionality.",
"author": "8b504054-ab46-4866-8a7e-46aaab29e54b",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"create_timestamp": "2024-01-09T00:09:39Z",
"edit_timestamp": "2024-01-16T15:52:14.891Z",
"tags": [
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
"28b160b5-5c8b-43a5-84d1-4179bde87e6f",
"72fb255c-132f-4124-802d-f4c051620540"
]
}
},
{
"model": "home.ticket",
"pk": "b0142315-b5c4-46a9-b02e-bdfdd6dffc37",
"fields": {
"title": "IT Training Request",
"description": "Requesting IT training sessions for the marketing team to enhance their proficiency in utilizing specific software tools. Providing a detailed outline of the desired training topics and the anticipated benefits for the team. Seeking assistance in scheduling and conducting the training sessions.",
"author": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"priority": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
"create_timestamp": "2024-01-09T00:12:17Z",
"edit_timestamp": "2024-01-16T15:52:02.743Z",
"tags": [
"72fb255c-132f-4124-802d-f4c051620540",
"cc473838-acaf-43f9-a601-dc4ab1f9026c"
]
}
},
{
"model": "home.ticket",
"pk": "c470a8b3-ca54-4324-abdf-1cfa4cdf70d6",
"fields": {
"title": "Email Configuration Assistance",
"description": "Need assistance in configuring email settings for a new team member. Providing the email client details and steps taken so far. Urgently seeking guidance to ensure the seamless setup of email accounts and communication channels for the new team member.",
"author": "94ca520d-4f5a-49c6-ab2b-9fbb362c51d9",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"create_timestamp": "2024-01-09T00:11:57Z",
"edit_timestamp": "2024-01-16T15:51:45.540Z",
"tags": [
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
"72fb255c-132f-4124-802d-f4c051620540"
]
}
},
{
"model": "home.ticket",
"pk": "dd5764ad-e765-4f9b-9a9b-a3a27e60c6dd",
"fields": {
"title": "General Inquiry",
"description": "Have a question regarding the new IT policies. Need clarification on specific guidelines. Please provide assistance at your earliest convenience.",
"author": "48ec50cd-d89b-41d7-bcb0-92612f0cf104",
"priority": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
"create_timestamp": "2024-01-09T00:07:26Z",
"edit_timestamp": "2024-01-16T15:51:38.099Z",
"tags": [
"dc4dc2d5-2784-4726-8a4e-99df35a143d2"
]
}
},
{
"model": "home.ticket",
"pk": "fd8a4ad9-04fb-4a0c-beac-e60e0739e881",
"fields": {
"title": "Software Update Problem",
"description": "Unable to install the latest software update on my workstation. Getting error code XYZ. Detailed steps attempted are listed in the description.",
"author": "c1984735-fdca-488b-b4e9-16bd01f3aebb",
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
"create_timestamp": "2024-01-09T00:06:32Z",
"edit_timestamp": "2024-01-16T15:51:31.695Z",
"tags": [
"0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
"72fb255c-132f-4124-802d-f4c051620540",
"cc473838-acaf-43f9-a601-dc4ab1f9026c"
]
}
},
{
"model": "home.ticket",
"fields": {
"title": "System Performance Analysis",
"description": "Experiencing slow system performance during peak hours. This issue is impacting our team's productivity. We need a thorough analysis of the system's performance bottlenecks and recommendations for optimization.",
"author": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
"tags": ["0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b", "e8c7e801-57c3-4699-a4f4-4308fb489f60"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Software Update Request",
"description": "We need assistance with updating our project management software to the latest version. There have been reported bugs in the current version impacting our workflow. Looking for guidance on a smooth update process and resolving any compatibility issues.",
"author": "94ca520d-4f5a-49c6-ab2b-9fbb362c51d9",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Network Connection Dropping",
"description": "Experiencing intermittent network connectivity issues across multiple devices. The problem seems to be affecting both wired and wireless connections. Urgently seeking assistance to diagnose and resolve the connectivity issues to ensure smooth workflow.",
"author": "811ad22b-5153-421f-bb84-6addcb8de570",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b", "28b160b5-5c8b-43a5-84d1-4179bde87e6f"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Database Performance Tuning",
"description": "The database queries are taking longer than usual, impacting overall system performance. Seeking expertise in optimizing database performance to enhance system responsiveness.",
"author": "4965745e-b82a-4496-80e1-055217a780b0",
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Security Audit Request",
"description": "Concerns about the security of our system. Requesting a comprehensive security audit to identify vulnerabilities and implement necessary measures for a secure environment.",
"author": "855d09bb-5919-4dff-8652-50aa1fb81b54",
"priority": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
"tags": ["0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b", "e8c7e801-57c3-4699-a4f4-4308fb489f60"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Software Training Request",
"description": "New software tools have been introduced, and the team requires training to use them efficiently. Looking for guidance on available training resources and scheduling training sessions.",
"author": "de7ef0f6-dbe8-49be-864c-19a94465c68a",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Email Configuration Issue",
"description": "Unable to send or receive emails through the company email system. Requesting assistance to troubleshoot and resolve the email configuration issue to ensure uninterrupted communication.",
"author": "811ad22b-5153-421f-bb84-6addcb8de570",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b", "28b160b5-5c8b-43a5-84d1-4179bde87e6f"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Server Downtime Investigation",
"description": "Frequent server downtime reported by users. Seeking an in-depth investigation to identify the root cause of the downtime and implement measures to prevent future occurrences.",
"author": "be2acfc8-7623-4f5f-864b-4163e07b881d",
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
"tags": ["0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b", "28b160b5-5c8b-43a5-84d1-4179bde87e6f"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Software License Renewal",
"description": "Our software licenses are expiring soon. Seeking assistance in renewing licenses and ensuring that all software tools are properly licensed to avoid any legal issues.",
"author": "811ad22b-5153-421f-bb84-6addcb8de570",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Hardware Upgrade Request",
"description": "The team is experiencing performance issues with outdated hardware. Requesting approval and assistance for a hardware upgrade to improve overall system performance.",
"author": "855d09bb-5919-4dff-8652-50aa1fb81b54",
"priority": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
"tags": ["e8c7e801-57c3-4699-a4f4-4308fb489f60", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Cleanliness Emergency - Bathroom Cleanup Needed",
"description": "Dear IT Support Team,<p>I hope this message finds you well. I am writing to bring to your attention a critical issue in the office bathrooms that requires immediate attention.</p><p>It seems there's a severe cleanliness problem, with shit found on the walls and even on the fucking ceiling! This is highly unhygienic and unacceptable, creating an uncomfortable environment for everyone using the facilities.</p><p>I understand that maintenance and cleanliness are not directly under the IT department's purview, but it would be greatly appreciated if you could liaise with the relevant personnel or department to ensure a swift resolution to this matter.</p><p>Your prompt action on this issue is crucial to maintain a healthy and professional working environment. Thank you for your attention to this urgent matter.</p><p>Best regards</p>",
"author": "d84476db-f240-41ba-af9c-b23eba22894e",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "User Access Permission Issue",
"description": "Some team members are facing issues accessing specific files and folders. Seeking assistance in resolving user access permission problems and ensuring that everyone has the required access.",
"author": "855d09bb-5919-4dff-8652-50aa1fb81b54",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b", "e8c7e801-57c3-4699-a4f4-4308fb489f60"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "VPN Not Working Again",
"description": "Unable to establish a stable VPN connection for remote team members. This is hindering collaboration and access to shared resources. Seeking assistance in resolving VPN connectivity problems.",
"author": "9cc67b83-2ccc-454e-be4a-9c772cb23d87",
"priority": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
"tags": ["0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b", "28b160b5-5c8b-43a5-84d1-4179bde87e6f"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Web Application Error",
"description": "Users are encountering frequent errors while using the web application. These errors disrupt the user experience. Seeking assistance in identifying and resolving issues within the web application.",
"author": "9f469a37-4d8d-4bd0-ba4e-16b07549f42a",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Collaboration Tool Setup",
"description": "Requesting assistance in setting up a new collaboration tool for team communication and project management. Need guidance on the best practices for efficient tool utilization.",
"author": "d84476db-f240-41ba-af9c-b23eba22894e",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Printer Connectivity Issue",
"description": "Facing difficulties in connecting to the office printer. This issue is impacting the ability to print important documents. Seeking assistance to resolve printer connectivity problems.",
"author": "855d09bb-5919-4dff-8652-50aa1fb81b54",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b", "28b160b5-5c8b-43a5-84d1-4179bde87e6f"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Mobile Device Configuration",
"description": "Team members are experiencing issues with configuring their mobile devices to access company resources. Seeking assistance in setting up mobile devices for secure and seamless access.",
"author": "855d09bb-5919-4dff-8652-50aa1fb81b54",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Software License Audit",
"description": "Conducting a comprehensive audit of software licenses to ensure compliance with legal requirements. Seeking guidance on the auditing process and recommendations for license optimization.",
"author": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "e8c7e801-57c3-4699-a4f4-4308fb489f60"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Remote Desktop Connection Issue",
"description": "Unable to establish a stable remote desktop connection. This is affecting remote team collaboration. Seeking assistance in troubleshooting and resolving remote desktop connectivity problems.",
"author": "9cc67b83-2ccc-454e-be4a-9c772cb23d87",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b", "28b160b5-5c8b-43a5-84d1-4179bde87e6f"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Software Deployment Request",
"description": "Requesting assistance in deploying new software across the team. Need guidance on the deployment process, best practices, and ensuring a smooth transition for all users.",
"author": "79ab62dc-878e-4563-8b82-1537de6f4a3c",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Audio-Visual Setup Assistance",
"description": "Setting up an audio-visual system for an upcoming presentation. Seeking assistance in configuring and optimizing audio and visual components for a successful presentation.",
"author": "d84476db-f240-41ba-af9c-b23eba22894e",
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Data Migration Assistance",
"description": "Planning to migrate data to a new server. Need guidance on the data migration process, ensuring data integrity, and minimizing downtime during the migration.",
"author": "9cc67b83-2ccc-454e-be4a-9c772cb23d87",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Training Room Setup",
"description": "Preparing for a team training session and need assistance in setting up the training room. Seeking guidance on audio-visual equipment, seating arrangements, and logistics.",
"author": "855d09bb-5919-4dff-8652-50aa1fb81b54",
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
"tags": ["e8c7e801-57c3-4699-a4f4-4308fb489f60", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Software Bug Report",
"description": "Users have encountered unexpected behavior in the software. Submitting a bug report to address and resolve the issues. Including detailed information on the encountered bugs.",
"author": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b", "e8c7e801-57c3-4699-a4f4-4308fb489f60"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Server Room Temperature Monitoring",
"description": "Concerns about the temperature in the server room. Seeking assistance in setting up temperature monitoring systems to ensure optimal conditions for server operation.",
"author": "79ab62dc-878e-4563-8b82-1537de6f4a3c",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["e8c7e801-57c3-4699-a4f4-4308fb489f60", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Software Update Request",
"description": "Requesting the latest software updates for critical applications. Seeking guidance on the update process, potential impacts, and ensuring minimal disruption to ongoing projects.",
"author": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "e8c7e801-57c3-4699-a4f4-4308fb489f60"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Software Update Request",
"description": "Requesting the latest software updates for critical applications. Seeking guidance on the update process, potential impacts, and ensuring minimal disruption to ongoing projects.",
"author": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "e8c7e801-57c3-4699-a4f4-4308fb489f60"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Smart Office IoT Implementation",
"description": "Exploring the implementation of IoT devices for a smart office environment. Seeking guidance on selecting suitable IoT devices, ensuring security, and optimizing the office environment.",
"author": "79ab62dc-878e-4563-8b82-1537de6f4a3c",
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
"tags": ["e8c7e801-57c3-4699-a4f4-4308fb489f60", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Network Performance Optimization",
"description": "Experiencing slow network performance affecting daily operations. Seeking assistance in optimizing network settings, identifying bottlenecks, and ensuring efficient data transfer.",
"author": "9cc67b83-2ccc-454e-be4a-9c772cb23d87",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["28b160b5-5c8b-43a5-84d1-4179bde87e6f", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Security Vulnerability Report",
"description": "Identified potential security vulnerabilities in the system. Submitting a report to address and mitigate security risks. Including details on vulnerabilities and suggested solutions.",
"author": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
"tags": ["0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b", "cc473838-acaf-43f9-a601-dc4ab1f9026c"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Password Reset Assistance",
"description": "Users experiencing difficulty in resetting their passwords. Seeking assistance in providing a secure and efficient process for password resets to ensure smooth access to accounts.",
"author": "855d09bb-5919-4dff-8652-50aa1fb81b54",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Software Training Request",
"description": "Team members requesting training sessions for newly implemented software. Seeking guidance on organizing training sessions, providing relevant resources, and ensuring a smooth learning experience.",
"author": "d84476db-f240-41ba-af9c-b23eba22894e",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Server Backup Configuration",
"description": "Reviewing and configuring server backup processes. Seeking assistance in ensuring regular and secure backups to prevent data loss and ensure business continuity.",
"author": "79ab62dc-878e-4563-8b82-1537de6f4a3c",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["e8c7e801-57c3-4699-a4f4-4308fb489f60", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Malware Incident - Files Missing, Desktop Black",
"description": "Employee reported a malware incident on their workstation. They've noticed that files are missing, and the desktop background has turned black. Immediate assistance is needed to assess the extent of the malware, recover any lost files, and ensure the workstation is secure. Requesting urgent attention to mitigate potential data loss and security risks.",
"author": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "New Hardware Request - Platinum Laptop Edition",
"description": "<p>Hello IT team, I hope this message finds you well. I'm writing to urgently request the latest Platinum Laptop Edition, which, according to my meticulous research (conducted during a particularly productive coffee break), is the only device that can unleash my full potential.</p><p>You see, the standard-issue laptops are simply holding me back from achieving peak productivity and enlightenment.</p><p>Now, you might wonder why I need the Platinum Laptop Edition. Well, it's not just a want; it's a dire necessity for my groundbreaking work in...uh, staring at the screensaver during brainstorming sessions. The sleek design and extra shine are crucial for creative inspiration.</p><p>Moreover, the built-in diamond-encrusted keyboard will undoubtedly elevate my typing skills to new heights. I firmly believe that the gentle caress of precious gems against my fingertips will unlock hidden depths of wisdom and efficiency.</p><p>In summary, the Platinum Laptop Edition is not just a laptop; it's the key to unlocking my true potential. I understand this may seem like an unnecessary request, but trust me, the future of innovation rests upon the shoulders of this laptop. Your prompt approval will undoubtedly lead to a revolution in... something.</p><p>Looking forward to your understanding and swift action on this matter. Together, we can usher in a new era of productivity and undeniable opulence.</p><p>Best regards, [Employee's Name]</p>",
"author": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Software License Renewal",
"description": "Notifying the IT department about an upcoming software license expiration. Requesting assistance in renewing the license to avoid disruptions in workflow. Please provide guidance on the renewal process.",
"author": "79ab62dc-878e-4563-8b82-1537de6f4a3c",
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "e8c7e801-57c3-4699-a4f4-4308fb489f60"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Mobile Device Setup",
"description": "Requesting assistance in setting up a new mobile device for work purposes. Need guidance on configuring email, security settings, and ensuring seamless synchronization with work applications.",
"author": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Meeting Room AV Setup",
"description": "Organizing an important presentation in the meeting room. Requesting assistance in setting up audiovisual equipment, ensuring proper connectivity, and conducting a brief test before the scheduled meeting.",
"author": "855d09bb-5919-4dff-8652-50aa1fb81b54",
"priority": "a680328f-0680-456c-8e26-f594e05989ad",
"tags": ["28b160b5-5c8b-43a5-84d1-4179bde87e6f", "e8c7e801-57c3-4699-a4f4-4308fb489f60"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Internet Connectivity Issues",
"description": "Experiencing intermittent internet connectivity issues. Need assistance in diagnosing the problem, identifying potential sources of disruption, and restoring stable internet access for uninterrupted work.",
"author": "9cc67b83-2ccc-454e-be4a-9c772cb23d87",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["28b160b5-5c8b-43a5-84d1-4179bde87e6f", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Employee Onboarding IT Setup",
"description": "New employee joining the team. Requesting IT assistance to set up their workstation with the necessary software, access permissions, and email configuration. Timely support is appreciated to ensure a smooth onboarding process.",
"author": "d84476db-f240-41ba-af9c-b23eba22894e",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "File Access Permission Issue",
"description": "Encountering difficulties accessing specific files. Requesting IT intervention to review and adjust file permissions, ensuring the necessary access for smooth workflow. Urgent attention is appreciated.",
"author": "c53ce609-5775-4ab6-8995-c22c1d96f00b",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Server Storage Expansion Request",
"description": "Experiencing limitations in server storage capacity. Requesting IT support to expand storage capacity, ensuring sufficient space for ongoing projects and data. Timely action is crucial to avoid disruptions.",
"author": "855d09bb-5919-4dff-8652-50aa1fb81b54",
"priority": "d140a5be-cf24-4250-8b38-31338e69dffd",
"tags": ["e8c7e801-57c3-4699-a4f4-4308fb489f60", "dc4dc2d5-2784-4726-8a4e-99df35a143d2"]
}
},
{
"model": "home.ticket",
"fields": {
"title": "Smartphone Email Sync Issue",
"description": "Encountering synchronization issues with work email on the smartphone. Requesting IT assistance to troubleshoot and resolve the problem, ensuring seamless access to emails on the go.",
"author": "9cc67b83-2ccc-454e-be4a-9c772cb23d87",
"priority": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"tags": ["cc473838-acaf-43f9-a601-dc4ab1f9026c", "28b160b5-5c8b-43a5-84d1-4179bde87e6f"]
}
}
]

View File

@ -1,11 +1,11 @@
[
{
"model": "home.ticketpriority",
"pk": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
"pk": "d140a5be-cf24-4250-8b38-31338e69dffd",
"fields": {
"title": "Low",
"colour": "#FFFFFF",
"backgroundcolour": "#66DD66"
"title": "Urgent",
"colour": "#c62828",
"backgroundcolour": "#ffcdd2"
}
},
{
@ -13,17 +13,8 @@
"pk": "a680328f-0680-456c-8e26-f594e05989ad",
"fields": {
"title": "High",
"colour": "#FFFFFF",
"backgroundcolour": "#FF884D"
}
},
{
"model": "home.ticketpriority",
"pk": "d140a5be-cf24-4250-8b38-31338e69dffd",
"fields": {
"title": "Urgent",
"colour": "#FFFFFF",
"backgroundcolour": "#FF4D4D"
"colour": "#ef6c00",
"backgroundcolour": "#ffe0b2"
}
},
{
@ -31,8 +22,17 @@
"pk": "e79687c6-9054-4706-b9a2-34afccfaa7c8",
"fields": {
"title": "Normal",
"colour": "#FFFFFF",
"backgroundcolour": "#66CC66"
"colour": "#2e7d32",
"backgroundcolour": "#c8e6c9"
}
},
{
"model": "home.ticketpriority",
"pk": "0ebc194c-b856-4e4f-9def-cd190d1e8d43",
"fields": {
"title": "Low",
"colour": "#388e3c",
"backgroundcolour": "#e8f5e9"
}
}
]
]

View File

@ -4,8 +4,8 @@
"pk": "0ac68e5d-9000-4fcb-bb44-40b1b0faaa2b",
"fields": {
"title": "Issue",
"colour": "#000000",
"backgroundcolour": "#FFB2B2"
"colour": "#e91e63",
"backgroundcolour": "#fce4ec"
}
},
{
@ -13,8 +13,8 @@
"pk": "28b160b5-5c8b-43a5-84d1-4179bde87e6f",
"fields": {
"title": "Network",
"colour": "#000000",
"backgroundcolour": "#BFD3C1"
"colour": "#512da8",
"backgroundcolour": "#d1c4e9"
}
},
{
@ -22,8 +22,8 @@
"pk": "72fb255c-132f-4124-802d-f4c051620540",
"fields": {
"title": "Requires Help",
"colour": "#000000",
"backgroundcolour": "#B2E57C"
"colour": "#388e3c",
"backgroundcolour": "#c8e6c9"
}
},
{
@ -31,8 +31,8 @@
"pk": "cc473838-acaf-43f9-a601-dc4ab1f9026c",
"fields": {
"title": "Software",
"colour": "#000000",
"backgroundcolour": "#FED8B1"
"colour": "#f57c00",
"backgroundcolour": "#ffe0b2"
}
},
{
@ -40,8 +40,8 @@
"pk": "dc4dc2d5-2784-4726-8a4e-99df35a143d2",
"fields": {
"title": "Question",
"colour": "#000000",
"backgroundcolour": "#FFFCB1"
"colour": "#00796b",
"backgroundcolour": "#b2dfdb"
}
},
{
@ -49,8 +49,8 @@
"pk": "e8c7e801-57c3-4699-a4f4-4308fb489f60",
"fields": {
"title": "Hardware",
"colour": "#FFFFFF",
"backgroundcolour": "#CCCCCC"
"colour": "#303f9f",
"backgroundcolour": "#c5cae9"
}
}
]

View File

@ -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',
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.16 on 2024-01-22 13:23
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('home', '0002_auto_20240112_1604'),
]
operations = [
migrations.AddField(
model_name='ticketpriority',
name='order',
field=models.PositiveIntegerField(default=0),
),
migrations.AddField(
model_name='tickettag',
name='order',
field=models.PositiveIntegerField(default=0),
),
]

View File

@ -1,7 +1,8 @@
# -*- encoding: utf-8 -*-
import uuid
import logging
import bleach
from uuid import uuid4
from datetime import timedelta, datetime
from django.db import models
@ -9,106 +10,121 @@ from django.conf import settings
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
log = logging.getLogger(__name__)
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)
backgroundcolour = models.CharField(max_length=7)
order = models.PositiveIntegerField(default=0, blank=False, null=False)
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)
backgroundcolour = models.CharField(max_length=7)
order = models.PositiveIntegerField(default=0, blank=False, null=False)
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.")
)
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=['b', 'i', 'u', 'p', 'br', 'a', 'h3', 'h4', 'h5', 'h6'],
tags=[
'b', 'i', 'u', 'p', 'br', 'a', 'h3', 'h4', 'h5', 'h6', 'strong',
'figure', 'table', 'tbody', 'tr', 'td'
],
attributes={'a': ['href', 'title']}
)
return cleaned_description
def short_description(self):
"""Provide a snippet of the description for presentation purposes."""
short_description = bleach.clean(self.description, tags=[])
if len(short_description) > 200:
return f"{short_description[:200]}..."
return short_description
def save(self, *args, **kwargs):
"""Override the save method to clean the description and apply timestamps."""
self.description = self.clean_description()
# self.edit_timestamp = timezone.now() ## TEMP COMMENT, UNCOMMENT LATER !!
now = timezone.now()
self.edit_timestamp = now
if self._state.adding:
self.create_timestamp = now
super().save(*args, **kwargs)
@property
@ -125,33 +141,44 @@ class Ticket(models.Model):
return self.create_timestamp != self.edit_timestamp
@property
def is_older_than_day(self) -> bool:
"""Returns boolean dependent on if `self.timestamp` is older than 24 hours.
def display_datetime(self) -> str:
"""Provides a human readable string representation of `self.timestamp` that should be displayed
to represent the ticket's age.
Returns
-------
bool
True if `self.timestamp` is older than 24 hours, False otherwise.
str
The string representation of `self.timestamp`.
"""
dayago = timezone.now() - timedelta(hours=24)
return self.timestamp <= dayago
difference = timezone.now() - self.timestamp
@property
def was_yesterday(self) -> bool:
"""_summary_
days = difference.days
hours = difference.total_seconds() // 3600
minutes = difference.total_seconds() // 60
seconds = difference.total_seconds()
Returns
-------
bool
_description_
"""
hours, minutes, seconds = map(int, (hours, minutes, seconds))
now = timezone.now()
midnight_today = now - timedelta(hours=now.hour, minutes=now.minute, seconds=now.second, microseconds=now.microsecond)
return self.timestamp < midnight_today
if seconds < 60:
value, unit = seconds, "second"
elif minutes < 60:
value, unit = minutes, "minute"
elif hours < 24:
value, unit = hours, "hour"
elif days < 7:
value, unit = days, "day"
else:
return self.timestamp.strftime("%Y-%m-%d")
if value > 1:
unit += "s"
return f"{value} {unit} ago"
@property
def timestamp(self) -> datetime:
@ -165,19 +192,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()]
}

View File

@ -1,25 +1,23 @@
# -*- encoding: utf-8 -*-
from django.urls import path, re_path, include
from django.urls import path, include
from django.shortcuts import redirect
from apps.home import views
from .views import TicketView
def reverse_to_index(reqeust):
return redirect("dashboard")
urlpatterns = [
# The home page
path('', views.index, name='home'),
# Custom Dashboard
path("", reverse_to_index, name="index"),
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"),
path('many/', views.get_tickets, name="ticket-getmany"),
])),
])),
# Matches any html file
re_path(r'^.*\.*', views.pages, name='pages'),
# # Matches any html file
# re_path(r'^.*\.*', views.pages, name='pages'),
]

View File

@ -1,5 +1,6 @@
# -*- encoding: utf-8 -*-
import json
from datetime import timedelta, datetime
from django import template
@ -9,8 +10,11 @@ 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 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
@ -20,29 +24,30 @@ def dashboard(request):
return render(request, "home/dashboard.html")
@login_required()
def tickets(request):
tickets = Ticket.objects.all().order_by("-create_timestamp")
priorities = TicketPriority.objects.all()
tags = TicketTag.objects.all()
departments = Department.objects.all()
class TicketView(TemplateView):
template_name = "home/tickets.html"
context = {
"tickets": tickets,
"priorities": priorities,
"tags": tags,
"departments": departments,
"dayago": datetime.now() - timedelta(hours=24)
}
@method_decorator(login_required)
def get(self, request):
priorities = TicketPriority.objects.all().order_by("order")
tags = TicketTag.objects.all().order_by("order")
departments = Department.objects.all().order_by("order")
return render(request, "home/tickets.html", context)
context = {
"priorities": priorities,
"tags": tags,
"departments": departments
}
return render(request, "home/tickets.html", context)
@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)
@ -50,26 +55,83 @@ def get_ticket(request):
@require_POST
def get_tickets(request):
filters = dict(request.POST.get("filters", {}))
filters = json.loads(request.POST.get("filters", "{}"))
queryset = Ticket.objects.all()
tickets = Ticket.objects.filter(**filters).order_by("-create_timestamp")
data = {"tickets": [ticket.serialize() for ticket in tickets]}
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")
serializer = TicketSerializer(tickets, many=True)
data = {"tickets": serializer.data}
return JsonResponse(data)
@login_required
@require_POST
def get_filter_counts(request):
priorities = TicketPriority.objects.all()
tags = TicketTag.objects.all()
departments = Department.objects.all()
tickets = Ticket.objects.all()
data = {
"priority_counts": {},
"tag_counts": {},
"department_counts": {},
"ticket_count": tickets.count()
}
for priority in priorities:
data["priority_counts"][str(priority.uuid)] = tickets.filter(priority=priority).count()
for tag in tags:
data["tag_counts"][str(tag.uuid)] = tickets.filter(tags__in=[tag]).count()
for department in departments:
data["department_counts"][str(department.uuid)] = tickets.filter(author__department=department).count()
return JsonResponse(data)
@login_required()
@require_POST
def new_ticket(request):
return JsonResponse({"placeholder": "nothing here yet"})
print(request.POST)
get = lambda key: request.POST.get(key)
getlist = lambda key: request.POST.getlist(key)
@login_required()
def index(request):
context = {'segment': 'index'}
title = get("title")
description = get("description")
author_id = get("author_id")
priority_id = get("priority_id")
tag_ids = getlist("tag_ids[]")
html_template = loader.get_template('home/index.html')
return HttpResponse(html_template.render(context, request))
User = get_user_model()
author = User.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()
@ -89,7 +151,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))

10924
apps/static/5.2_bootstrap.css Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

45594
apps/static/css/adminator.css Normal file

File diff suppressed because it is too large Load Diff

12068
apps/static/css/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

5286
apps/static/css/colours.css Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3021
apps/static/css/fontawesome.css vendored Normal file

File diff suppressed because it is too large Load Diff

232
apps/static/css/index.css Normal file
View File

@ -0,0 +1,232 @@
@charset "UTF-8";
:root,
[data-bs-theme="light"] {
--border-colour: var(--bs-light-border-subtle);
}
[data-bs-theme="dark"] {
--border-colour: var(--bs-dark-border-subtle)
}
.select2-container {
z-index: 99999;
}
.select2-selection__choice {
display: flex;
align-items: center;
padding-left: 3px !important;
}
.select2-selection__choice > button {
border: none;
background-color: var(--bs-white-rgb);
}
.select2-results__option--selected { display: none; }
.ck-editor .ck.ck-editor__top .ck-toolbar {
border-top-left-radius: 0.375rem !important;
border-top-right-radius: 0.375rem !important;
}
.ck-editor .ck.ck-editor__main {
overflow: hidden;
}
.ck-editor .ck.ck-editor__main > div {
border-bottom-left-radius: 0.375rem !important;
border-bottom-right-radius: 0.375rem !important;
}
.ck-editor__editable[role="textbox"] {
/* editing area */
min-height: 200px;
max-height: 200px;
}
#ticketsContainer .loading .spinner-grow {
width: .8rem;
height: .8rem;
}
#ticketsContainer .loading .spinner-grow:nth-child(1) {
animation-delay: 0s;
}
#ticketsContainer .loading .spinner-grow:nth-child(2) {
animation-delay: .9s;
}
#ticketsContainer .loading .spinner-grow:nth-child(3) {
animation-delay: 1.8s;
}
.bg-none {
background: none;
}
.border-none {
border: none
}
a {
text-decoration: none;
}
body {
font-family: Roboto, -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
font-size: 14px;
color: #72777a;
line-height: 1.5;
letter-spacing: 0.2px;
overflow-x: hidden;
}
.bdT {
border-top: 1px solid var(--border-colour) !important;
}
.bdB {
border-bottom: 1px solid var(--border-colour) !important;
}
.w-fc {
width: fit-content;
}
.h-fc {
height: fit-content;
}
/* Ticket List Items */
.ticket-item {
cursor: pointer;
}
.ticket-item.active {
background-color: var(--bs-tertiary-bg);
}
.ticket-item:hover {
background-color: var(--bs-tertiary-bg);
}
/*
.ticket-item .ticket-item-indicator {
writing-mode: vertical-rl;
text-orientation: upright;
} */
.ticket-item .ticket-item-complex {
transition: all 0.3s ease;
overflow: hidden;
width: 2rem;
}
#ticketsContainer:not(.complex-items) .ticket-item .ticket-item-complex {
margin: 0 !important;
width: 0 !important;
}
.ticket-item .ticket-item-complex .badge {
min-width: 1.8rem;
}
.ticket-item .ticket-item-icon {
width: 2rem;
height: 2rem;
border-radius: 50%;
object-fit: cover;
}
.ticket-item .ticket-item-author {
margin-bottom: 0;
}
.ticket-item .ticket-item-title {
color: var(--bs-body-color);
font-size: 1rem;
text-transform: capitalize;
}
.ticket-item .ticket-item-desc {
color: var(--bs-secondary-color);
width: 100%;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
max-height: 3rem;
}
.ticket-item .ticket-item-tags .ticket-content-badge:last-child {
margin-right: 0 !important;
}
.loading .email-list-item {
cursor: progress;
}
/* Ticket Content */
.ticket-content .ticket-content-title {
color: var(--bs-body-color);
}
.ticket-content .ticket-content-desc {
color: var(--bs-secondary-color);
}
.ticket-content .ticket-content-icon {
border-radius: 50%;
width: 3rem;
height: 3rem;
object-fit: cover;
}
.ticket-content .ticket-content.author {
/* color: var(--bs-tertiary-color); */
margin-bottom: 5px;
}
/* Tickets Side Nav */
.email-app .email-side-nav {
border-right: 1px solid var(--border-colour);
}
.email-app .email-side-nav .nav-item .nav-link {
color: var(--bs-tertiary-color);
}
.email-app .email-wrapper {
min-height: 0;
}
@media screen and (min-width: 992px) {
.email-app .email-wrapper .email-list {
border-right: 1px solid var(--border-colour);
}
}
/* Base Sidebar Navigation */
.sidebar-menu {
border-right: 1px solid var(--border-colour);
}
.sidebar-logo {
border-right: 1px solid var(--border-colour);
border-bottom: 1px solid var(--border-colour);
}
/* Top Navigation */
.header {
border-bottom: 1px solid var(--border-colour);
}

View File

@ -0,0 +1,136 @@
svg {
touch-action: none;
}
.jvectormap-container {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
touch-action: none;
}
.jvectormap-tip {
position: absolute;
display: none;
border: solid 1px #CDCDCD;
border-radius: 3px;
background: #292929;
color: white;
font-family: sans-serif, Verdana;
font-size: smaller;
padding: 3px;
}
.jvectormap-zoomin, .jvectormap-zoomout, .jvectormap-goback {
position: absolute;
left: 10px;
border-radius: 3px;
background: #292929;
padding: 3px;
color: white;
cursor: pointer;
line-height: 10px;
text-align: center;
box-sizing: content-box;
}
.jvectormap-zoomin, .jvectormap-zoomout {
width: 10px;
height: 10px;
}
.jvectormap-zoomin {
top: 10px;
}
.jvectormap-zoomout {
top: 30px;
}
.jvectormap-goback {
bottom: 10px;
z-index: 1000;
padding: 6px;
}
.jvectormap-spinner {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: center no-repeat url(data:image/gif;base64,R0lGODlhIAAgAPMAAP///wAAAMbGxoSEhLa2tpqamjY2NlZWVtjY2OTk5Ly8vB4eHgQEBAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAIAAgAAAE5xDISWlhperN52JLhSSdRgwVo1ICQZRUsiwHpTJT4iowNS8vyW2icCF6k8HMMBkCEDskxTBDAZwuAkkqIfxIQyhBQBFvAQSDITM5VDW6XNE4KagNh6Bgwe60smQUB3d4Rz1ZBApnFASDd0hihh12BkE9kjAJVlycXIg7CQIFA6SlnJ87paqbSKiKoqusnbMdmDC2tXQlkUhziYtyWTxIfy6BE8WJt5YJvpJivxNaGmLHT0VnOgSYf0dZXS7APdpB309RnHOG5gDqXGLDaC457D1zZ/V/nmOM82XiHRLYKhKP1oZmADdEAAAh+QQJCgAAACwAAAAAIAAgAAAE6hDISWlZpOrNp1lGNRSdRpDUolIGw5RUYhhHukqFu8DsrEyqnWThGvAmhVlteBvojpTDDBUEIFwMFBRAmBkSgOrBFZogCASwBDEY/CZSg7GSE0gSCjQBMVG023xWBhklAnoEdhQEfyNqMIcKjhRsjEdnezB+A4k8gTwJhFuiW4dokXiloUepBAp5qaKpp6+Ho7aWW54wl7obvEe0kRuoplCGepwSx2jJvqHEmGt6whJpGpfJCHmOoNHKaHx61WiSR92E4lbFoq+B6QDtuetcaBPnW6+O7wDHpIiK9SaVK5GgV543tzjgGcghAgAh+QQJCgAAACwAAAAAIAAgAAAE7hDISSkxpOrN5zFHNWRdhSiVoVLHspRUMoyUakyEe8PTPCATW9A14E0UvuAKMNAZKYUZCiBMuBakSQKG8G2FzUWox2AUtAQFcBKlVQoLgQReZhQlCIJesQXI5B0CBnUMOxMCenoCfTCEWBsJColTMANldx15BGs8B5wlCZ9Po6OJkwmRpnqkqnuSrayqfKmqpLajoiW5HJq7FL1Gr2mMMcKUMIiJgIemy7xZtJsTmsM4xHiKv5KMCXqfyUCJEonXPN2rAOIAmsfB3uPoAK++G+w48edZPK+M6hLJpQg484enXIdQFSS1u6UhksENEQAAIfkECQoAAAAsAAAAACAAIAAABOcQyEmpGKLqzWcZRVUQnZYg1aBSh2GUVEIQ2aQOE+G+cD4ntpWkZQj1JIiZIogDFFyHI0UxQwFugMSOFIPJftfVAEoZLBbcLEFhlQiqGp1Vd140AUklUN3eCA51C1EWMzMCezCBBmkxVIVHBWd3HHl9JQOIJSdSnJ0TDKChCwUJjoWMPaGqDKannasMo6WnM562R5YluZRwur0wpgqZE7NKUm+FNRPIhjBJxKZteWuIBMN4zRMIVIhffcgojwCF117i4nlLnY5ztRLsnOk+aV+oJY7V7m76PdkS4trKcdg0Zc0tTcKkRAAAIfkECQoAAAAsAAAAACAAIAAABO4QyEkpKqjqzScpRaVkXZWQEximw1BSCUEIlDohrft6cpKCk5xid5MNJTaAIkekKGQkWyKHkvhKsR7ARmitkAYDYRIbUQRQjWBwJRzChi9CRlBcY1UN4g0/VNB0AlcvcAYHRyZPdEQFYV8ccwR5HWxEJ02YmRMLnJ1xCYp0Y5idpQuhopmmC2KgojKasUQDk5BNAwwMOh2RtRq5uQuPZKGIJQIGwAwGf6I0JXMpC8C7kXWDBINFMxS4DKMAWVWAGYsAdNqW5uaRxkSKJOZKaU3tPOBZ4DuK2LATgJhkPJMgTwKCdFjyPHEnKxFCDhEAACH5BAkKAAAALAAAAAAgACAAAATzEMhJaVKp6s2nIkolIJ2WkBShpkVRWqqQrhLSEu9MZJKK9y1ZrqYK9WiClmvoUaF8gIQSNeF1Er4MNFn4SRSDARWroAIETg1iVwuHjYB1kYc1mwruwXKC9gmsJXliGxc+XiUCby9ydh1sOSdMkpMTBpaXBzsfhoc5l58Gm5yToAaZhaOUqjkDgCWNHAULCwOLaTmzswadEqggQwgHuQsHIoZCHQMMQgQGubVEcxOPFAcMDAYUA85eWARmfSRQCdcMe0zeP1AAygwLlJtPNAAL19DARdPzBOWSm1brJBi45soRAWQAAkrQIykShQ9wVhHCwCQCACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiRMDjI0Fd30/iI2UA5GSS5UDj2l6NoqgOgN4gksEBgYFf0FDqKgHnyZ9OX8HrgYHdHpcHQULXAS2qKpENRg7eAMLC7kTBaixUYFkKAzWAAnLC7FLVxLWDBLKCwaKTULgEwbLA4hJtOkSBNqITT3xEgfLpBtzE/jiuL04RGEBgwWhShRgQExHBAAh+QQJCgAAACwAAAAAIAAgAAAE7xDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfZiCqGk5dTESJeaOAlClzsJsqwiJwiqnFrb2nS9kmIcgEsjQydLiIlHehhpejaIjzh9eomSjZR+ipslWIRLAgMDOR2DOqKogTB9pCUJBagDBXR6XB0EBkIIsaRsGGMMAxoDBgYHTKJiUYEGDAzHC9EACcUGkIgFzgwZ0QsSBcXHiQvOwgDdEwfFs0sDzt4S6BK4xYjkDOzn0unFeBzOBijIm1Dgmg5YFQwsCMjp1oJ8LyIAACH5BAkKAAAALAAAAAAgACAAAATwEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GGl6NoiPOH16iZKNlH6KmyWFOggHhEEvAwwMA0N9GBsEC6amhnVcEwavDAazGwIDaH1ipaYLBUTCGgQDA8NdHz0FpqgTBwsLqAbWAAnIA4FWKdMLGdYGEgraigbT0OITBcg5QwPT4xLrROZL6AuQAPUS7bxLpoWidY0JtxLHKhwwMJBTHgPKdEQAACH5BAkKAAAALAAAAAAgACAAAATrEMhJaVKp6s2nIkqFZF2VIBWhUsJaTokqUCoBq+E71SRQeyqUToLA7VxF0JDyIQh/MVVPMt1ECZlfcjZJ9mIKoaTl1MRIl5o4CUKXOwmyrCInCKqcWtvadL2SYhyASyNDJ0uIiUd6GAULDJCRiXo1CpGXDJOUjY+Yip9DhToJA4RBLwMLCwVDfRgbBAaqqoZ1XBMHswsHtxtFaH1iqaoGNgAIxRpbFAgfPQSqpbgGBqUD1wBXeCYp1AYZ19JJOYgH1KwA4UBvQwXUBxPqVD9L3sbp2BNk2xvvFPJd+MFCN6HAAIKgNggY0KtEBAAh+QQJCgAAACwAAAAAIAAgAAAE6BDISWlSqerNpyJKhWRdlSAVoVLCWk6JKlAqAavhO9UkUHsqlE6CwO1cRdCQ8iEIfzFVTzLdRAmZX3I2SfYIDMaAFdTESJeaEDAIMxYFqrOUaNW4E4ObYcCXaiBVEgULe0NJaxxtYksjh2NLkZISgDgJhHthkpU4mW6blRiYmZOlh4JWkDqILwUGBnE6TYEbCgevr0N1gH4At7gHiRpFaLNrrq8HNgAJA70AWxQIH1+vsYMDAzZQPC9VCNkDWUhGkuE5PxJNwiUK4UfLzOlD4WvzAHaoG9nxPi5d+jYUqfAhhykOFwJWiAAAIfkECQoAAAAsAAAAACAAIAAABPAQyElpUqnqzaciSoVkXVUMFaFSwlpOCcMYlErAavhOMnNLNo8KsZsMZItJEIDIFSkLGQoQTNhIsFehRww2CQLKF0tYGKYSg+ygsZIuNqJksKgbfgIGepNo2cIUB3V1B3IvNiBYNQaDSTtfhhx0CwVPI0UJe0+bm4g5VgcGoqOcnjmjqDSdnhgEoamcsZuXO1aWQy8KAwOAuTYYGwi7w5h+Kr0SJ8MFihpNbx+4Erq7BYBuzsdiH1jCAzoSfl0rVirNbRXlBBlLX+BP0XJLAPGzTkAuAOqb0WT5AH7OcdCm5B8TgRwSRKIHQtaLCwg1RAAAOwAAAAAAAAAAAA==);
}
.jvectormap-legend-title {
font-weight: bold;
font-size: 14px;
text-align: center;
}
.jvectormap-legend-cnt {
position: absolute;
}
.jvectormap-legend-cnt-h {
bottom: 0;
right: 0;
}
.jvectormap-legend-cnt-v {
top: 0;
right: 0;
}
.jvectormap-legend {
background: black;
color: white;
border-radius: 3px;
}
.jvectormap-legend-cnt-h .jvectormap-legend {
float: left;
margin: 0 10px 10px 0;
padding: 3px 3px 1px 3px;
}
.jvectormap-legend-cnt-h .jvectormap-legend .jvectormap-legend-tick {
float: left;
}
.jvectormap-legend-cnt-v .jvectormap-legend {
margin: 10px 10px 0 0;
padding: 3px;
}
.jvectormap-legend-cnt-h .jvectormap-legend-tick {
width: 40px;
}
.jvectormap-legend-cnt-h .jvectormap-legend-tick-sample {
height: 15px;
}
.jvectormap-legend-cnt-v .jvectormap-legend-tick-sample {
height: 20px;
width: 20px;
display: inline-block;
vertical-align: middle;
}
.jvectormap-legend-tick-text {
font-size: 12px;
}
.jvectormap-legend-cnt-h .jvectormap-legend-tick-text {
text-align: center;
}
.jvectormap-legend-cnt-v .jvectormap-legend-tick-text {
display: inline-block;
vertical-align: middle;
line-height: 20px;
padding-left: 3px;
}

View File

@ -0,0 +1,116 @@
/*
* Container style
*/
.ps {
overflow: hidden !important;
overflow-anchor: none;
-ms-overflow-style: none;
touch-action: auto;
-ms-touch-action: auto;
}
/*
* Scrollbar rail styles
*/
.ps__rail-x {
display: none;
opacity: 0;
transition: background-color .2s linear, opacity .2s linear;
-webkit-transition: background-color .2s linear, opacity .2s linear;
height: 15px;
/* there must be 'bottom' or 'top' for ps__rail-x */
bottom: 0px;
/* please don't change 'position' */
position: absolute;
}
.ps__rail-y {
display: none;
opacity: 0;
transition: background-color .2s linear, opacity .2s linear;
-webkit-transition: background-color .2s linear, opacity .2s linear;
width: 15px;
/* there must be 'right' or 'left' for ps__rail-y */
right: 0;
/* please don't change 'position' */
position: absolute;
}
.ps--active-x > .ps__rail-x,
.ps--active-y > .ps__rail-y {
display: block;
background-color: transparent;
}
.ps:hover > .ps__rail-x,
.ps:hover > .ps__rail-y,
.ps--focus > .ps__rail-x,
.ps--focus > .ps__rail-y,
.ps--scrolling-x > .ps__rail-x,
.ps--scrolling-y > .ps__rail-y {
opacity: 0.6;
}
.ps .ps__rail-x:hover,
.ps .ps__rail-y:hover,
.ps .ps__rail-x:focus,
.ps .ps__rail-y:focus,
.ps .ps__rail-x.ps--clicking,
.ps .ps__rail-y.ps--clicking {
background-color: #eee;
opacity: 0.9;
}
/*
* Scrollbar thumb styles
*/
.ps__thumb-x {
background-color: #aaa;
border-radius: 6px;
transition: background-color .2s linear, height .2s ease-in-out;
-webkit-transition: background-color .2s linear, height .2s ease-in-out;
height: 6px;
/* there must be 'bottom' for ps__thumb-x */
bottom: 2px;
/* please don't change 'position' */
position: absolute;
}
.ps__thumb-y {
background-color: #aaa;
border-radius: 6px;
transition: background-color .2s linear, width .2s ease-in-out;
-webkit-transition: background-color .2s linear, width .2s ease-in-out;
width: 6px;
/* there must be 'right' for ps__thumb-y */
right: 2px;
/* please don't change 'position' */
position: absolute;
}
.ps__rail-x:hover > .ps__thumb-x,
.ps__rail-x:focus > .ps__thumb-x,
.ps__rail-x.ps--clicking .ps__thumb-x {
background-color: #999;
height: 11px;
}
.ps__rail-y:hover > .ps__thumb-y,
.ps__rail-y:focus > .ps__thumb-y,
.ps__rail-y.ps--clicking .ps__thumb-y {
background-color: #999;
width: 11px;
}
/* MS supports */
@supports (-ms-overflow-style: none) {
.ps {
overflow: auto !important;
}
}
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
.ps {
overflow: auto !important;
}
}

File diff suppressed because it is too large Load Diff

View File

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 434 KiB

View File

Before

Width:  |  Height:  |  Size: 229 KiB

After

Width:  |  Height:  |  Size: 229 KiB

View File

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

View File

Before

Width:  |  Height:  |  Size: 492 KiB

After

Width:  |  Height:  |  Size: 492 KiB

View File

Before

Width:  |  Height:  |  Size: 160 B

After

Width:  |  Height:  |  Size: 160 B

View File

Before

Width:  |  Height:  |  Size: 148 B

After

Width:  |  Height:  |  Size: 148 B

View File

Before

Width:  |  Height:  |  Size: 201 B

After

Width:  |  Height:  |  Size: 201 B

View File

Before

Width:  |  Height:  |  Size: 158 B

After

Width:  |  Height:  |  Size: 158 B

View File

Before

Width:  |  Height:  |  Size: 146 B

After

Width:  |  Height:  |  Size: 146 B

View File

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

373
apps/static/js/_tickets.js Normal file
View File

@ -0,0 +1,373 @@
var displayedTicketID = -1;
filters = {"ordering": "-edit_timestamp", "page_size": 100};
editor = null;
searchTimeout = null;
loadingTickets = false;
const formControls = [
{
id: "newTitle",
validation: function(element) {
const value = element.val();
return (!element.attr("required") || value.trim() !== "")
},
errorMessage: function(element) {
return "This field is required."
}
}
];
$(document).ready(function() {
ClassicEditor
.create( document.getElementById("newDesc"), {})
.then( newEditor => {
editor = newEditor;
})
.catch( error => {
console.error(error)
});
$("#searchTickets").keyup(() => {
$("#ticketsContainer .content").empty();
$("#ticketsContainer .none-found").hide();
$("#ticketsContainer .loading").show();
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
console.debug("searching");
value = $("#searchTickets").val();
if (value === "") {
console.debug("deleted search filters");
delete filters["search"];
}
else {
console.debug("updated search filters");
filters["search"] = value;
}
loadAllTickets();
}, 500);
})
setupFilter("#filterSidebar .filter-department", "author__department");
setupFilter("#filterSidebar .filter-tags", "tags");
setupFilter("#filterSidebar .filter-priority", "priority");
loadFilterCounts();
loadAllTickets();
$(".select2-selection__choice").each(function() {
// Work out how to apply the colours of the tags to the selection choices.
})
});
function setupFilter(selector, key) {
$(selector).each(function () {
var input = $(this).find("input[type=checkbox], input[type=radio]");
var uuid = input.val();
input.on("change", function () {
if (input.is(":checkbox")) {
if ($(this).is(":checked")) {
filters[key] = filters[key] || [];
filters[key].push(uuid);
}
else {
filters[key] = filters[key].filter(id => id !== uuid);
if (filters[key].length === 0) {
delete filters[key];
}
}
}
else if (input.is(":radio") && input.is(":checked")) {
if (uuid === "all") {
delete filters[key];
}
else {
filters[key] = [uuid];
}
}
console.debug(`Filter applied '${key}' as '${uuid}'`)
loadAllTickets();
});
});
}
function validateForm() {
$("#ticketModal form").find(".form-control,.form-select").removeClass("is-valid is-invalid");
$("#ticketModal form .invalid-feedback").text("");
var valid = true;
formControls.forEach(function(control) {
var element = $("#" + control.id);
if (!control.validation(element)) {
element.addClass("is-invalid");
element.siblings(".invalid-feedback").text(control.errorMessage(element));
valid = false;
}
else {
element.addClass("is-valid");
}
});
return valid;
}
$("#ticketModal form").on("submit", function(event) {
event.preventDefault();
if (!validateForm()) {
return;
}
$.ajax({
url: URL_NewTicket,
type: "POST",
dataType: "json",
data: {
csrfmiddlewaretoken: CSRFMiddlewareToken,
title: $("#newTitle").val(),
description: editor.getData(),
author_id: CurrentUserID,
priority_id: $("#newPriority").val(),
tag_ids: $("#newTags").val()
},
success: function(data) {
loadAllTickets();
loadFilterCounts();
},
error: function(data) {
alert(JSON.stringify(data, null, 4))
}
});
});
function getOrdinalSuffix(day) {
if (day >= 11 && day <= 13) {
return day + 'th';
} else {
switch (day % 10) {
case 1: return day + 'st';
case 2: return day + 'nd';
case 3: return day + 'rd';
default: return day + 'th';
}
}
}
function updateFilterCounts(filterType, data) {
$("#filterSidebar .filter-" + filterType).each(function() {
var uuid = $(this).find("input[type=checkbox],input[type=radio]").val();
var count = data[filterType][uuid];
$(this).find(".badge").text(count);
});
}
function loadFilterCounts() {
$.ajax({
url: URL_FilterCounts,
type: "GET",
success: function(data) {
updateFilterCounts('priority', data);
updateFilterCounts('tags', data);
updateFilterCounts('department', data);
$("#filterPriorityAll .badge").text(data.tickets);
$("#filterDepartmentAll .badge").text(data.tickets)
$("#ticketCounts .total").text(data.tickets)
},
error: function(data) {
console.error(JSON.stringify(data, null, 4))
}
});
}
function timestampToHumanDate(timestamp, wasYesterday) {
if (wasYesterday) {
var day = getOrdinalSuffix(timestamp.getDate());
var month = timestamp.toLocaleString('en-GB', { month: 'short' });
var year = timestamp.toLocaleString('en-GB', { year: 'numeric' });
var time = timestamp.toLocaleString('en-GB', { hour: 'numeric', minute: 'numeric' });
return time + ', ' + day + ' ' + month + ' ' + year;
}
var hours = timestamp.getUTCHours();
var minutes = timestamp.getUTCMinutes();
return hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0');
}
function loadAllTickets() {
if (loadingTickets === true) {
return;
}
$("#ticketsContainer .content").empty();
$("#ticketsContainer .none-found").hide();
$("#ticketsContainer .loading").show();
loadingTickets = true;
// alert(JSON.stringify(filters, null, 4));
$.ajax({
url: URL_Tickets,
type: "GET",
dataType: "json",
data: $.param(filters, true),
success: function(data) {
loadingTickets = false;
// console.log(JSON.stringify(data, null, 4))
$("#ticketCounts .current").text(data.results.length);
$("#ticketsContainer .loading").hide();
if (data.results.length === 0) $("#ticketsContainer .none-found").show();
else $("#ticketsContainer .none-found").hide();
data.results.forEach(function(ticket) {
var timestamp = new Date(ticket.timestamp);
var formattedTime = timestampToHumanDate(timestamp, ticket.was_yesterday);
if (ticket.is_edited) {
formattedTime += " • edited";
}
var template = $($("#ticketItemTemplate").html());
template.find(".ticket-item-author").text(`${ticket.author.forename} ${ticket.author.surname}`);
template.find(".ticket-item-datetime").text(formattedTime);
template.find(".ticket-item-title").text(ticket.title);
template.find(".ticket-item-desc").text(ticket.description);
template.find(".ticket-item-icon").attr("src", ticket.author.icon);
template.attr("data-uuid", ticket.uuid);
$("#ticketsContainer .content").append(template);
});
applyTicketClickFunction();
},
error: function(data) {
loadingTickets = false;
$("#ticketsContainer .content").empty();
$("#ticketsContainer .none-found").hide();
$("#ticketsContainer .loading").hide();
if (data.status === 429) {
alert(`HTTP ${data.status} - ${data.statusText}\n${data.responseJSON.detail}`)
}
}
});
}
function applyTicketClickFunction() {
$(".ticket-item").on("click", function(e) {
e.preventDefault();
displayTicket(this);
$('.email-app').removeClass('side-active');
$('.email-content').toggleClass('open');
});
}
function reloadCurrentTicket() {
displayTicket($(".ticket-item.bgc-grey-100"));
}
function changeTicket(next=true) {
var selectedTicket = $(".ticket-item.bgc-grey-100");
if (!selectedTicket.length) {
displayTicket($(".ticket-item").first());
return;
}
if (next) {
displayTicket(selectedTicket.next());
}
else {
displayTicket(selectedTicket.prev());
}
if (!$('.email-content').hasClass('open')) {
$('.email-content').addClass('open');
}
}
function displayTicket(ticketElement) {
ticket = $(ticketElement);
ticketID = ticket.data("uuid");
// $(".back-to-mailbox").off("click").on("click", function(event) {
// event.preventDefault();
// $('.email-content').toggleClass('open');
// displayTicket(ticketElement);
// });
if (displayedTicketID === ticketID) {
// displayedTicketID = -1;
return;
}
ticket.siblings().removeClass("bgc-grey-100");
ticket.addClass("bgc-grey-100");
$("#ticketTitle").text("")
$("#ticketDesc").empty();
$("#ticketAuthor").text("");
$("#ticketAuthorImg").hide();
$("#ticketAuthorImg").prop("src", "");
$("#ticketTimestamp").text("");
$("#btnGroupDrop2").hide();
$("#ticketBadges").empty().hide();
displayedTicketID = ticketID;
$.ajax({
url: URL_Tickets,
type: 'get',
dataType: 'json',
data: $.param({uuid: ticketID}, true),
success: function (data) {
console.log(JSON.stringify(data, null, 4));
var ticket = data.results[0];
var author = ticket.author;
var department = author.department;
var priority = ticket.priority;
$("#ticketTitle").text(ticket.title);
$("#ticketDesc").append($(`<div class="w-100">${ticket.description}</div>`));
$("#ticketAuthor").text(`${author.forename} ${author.surname}`);
$("#ticketAuthorImg").show();
$("#ticketAuthorImg").prop("src", author.icon);
$("#btnGroupDrop2").show();
$("#ticketBadges").show();
$("#ticketBadges").append($(`<div class="badge me-1" style="color: ${priority.colour}; background-color: ${priority.backgroundcolour};">${priority.title} Priority <i class="ti-control-record "></i></div>`));
if (department != null) {
$("#ticketBadges").append($(`<div class="badge bgc-deep-purple-500 me-1">${department.title}</div>`));
}
ticket.tags.forEach(function(tag) {
$("#ticketBadges").append($(`<div class="badge me-1" style="color: ${tag.colour}; background-color: ${tag.backgroundcolour};">${tag.title} <i class="ti-tag"></i></div>`));
});
// timestamp
var timestamp = new Date(ticket.timestamp);
var formattedTime = timestampToHumanDate(timestamp, ticket.was_yesterday);
if (ticket.is_edited) {
formattedTime += " • edited";
}
$("#ticketTimestamp").text(formattedTime);
},
error: function(message) {
alert(JSON.stringify(message, null, 4));
}
});
}

22
apps/static/js/base.js Normal file
View File

@ -0,0 +1,22 @@
$(document).ready(function() {
// Activate all tooltips
$('[data-bs-toggle="tooltip"]').tooltip();
// Apply the user preferred theme
const theme = localStorage.getItem("theme");
if (theme == "light" || theme == "dark") {
$("body").attr("data-bs-theme", theme);
}
else {
$("body").attr("data-bs-theme", "light");
}
});
$("#themeToggle").on("click", function() {
var theme = $("body").attr("data-bs-theme");
theme = theme == "light" ? "dark" : "light";
localStorage.setItem("theme", theme)
$("body").attr("data-bs-theme", theme);
});

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

442
apps/static/js/tickets.js Normal file
View File

@ -0,0 +1,442 @@
var filters = {"ordering": "-edit_timestamp", "strict-tags": true};
global_loadingTickets = false;
searchTimeout = null;
pagination = {};
complexItems = false;
$(document).ready(function() {
initSearchBar();
toggleComplexItems(localStorage.getItem("hideComplexTickets") === "true");
setupFilter("#filterSidebar .filter-department", "author__department");
setupFilter("#filterSidebar .filter-tags", "tags");
setupFilter("#filterSidebar .filter-priority", "priority");
loadFilterCounts();
loadTicketItems();
});
function updateItemsState(state) {
console.debug(`updating items state to '${state}'`);
$("#ticketsContainer").scrollTop(0);
$("#ticketsContainer").trigger("updateScrollbar");
switch (state) {
case "content":
$("#ticketsContainer .none-found").hide();
$("#ticketsContainer .loading").hide();
$("#filterSidebar input").prop("disabled", false);
break;
case "loading":
$("#ticketsContainer .content").empty();
$("#ticketsContainer .none-found").hide();
$("#ticketsContainer .loading").show();
$("#filterSidebar input").prop("disabled", true);
break;
case "no-content":
$("#ticketsContainer .content").empty();
$("#ticketsContainer .none-found").show();
$("#ticketsContainer .loading").hide();
$("#filterSidebar input").prop("disabled", false);
break;
default:
throw new Error(`Invalid Items State '${state}'`);
}
}
function updateContentState(state) {
console.debug(`updating content state to '${state}'`);
switch (state) {
case "content":
$("#ticketContent .loading").hide();
break;
case "loading":
$("#ticketContent .content").empty();
$("#ticketContent .loading").show();
break;
default:
throw new Error(`Invalid Content State '${state}'`);
}
}
function initSearchBar() {
$("#searchTickets").keyup(() => {
updateItemsState("loading");
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
console.debug("searching");
value = $("#searchTickets").val();
if (value === "") {
console.debug("deleted search filters");
delete filters["search"];
}
else {
console.debug("updated search filters");
filters["search"] = value;
}
loadTicketItems();
}, 500);
})
}
function setupFilter(selector, key) {
$(selector).each(function () {
var input = $(this).find("input[type=checkbox], input[type=radio]");
var uuid = input.val();
input.on("change", function () {
if (input.is(":checkbox")) {
if ($(this).is(":checked")) {
filters[key] = filters[key] || [];
filters[key].push(uuid);
}
else {
filters[key] = filters[key].filter(id => id !== uuid);
if (filters[key].length === 0) delete filters[key];
}
}
else if (input.is(":radio") && input.is(":checked")) {
if (uuid === "all") delete filters[key];
else filters[key] = [uuid];
const deselectOption = $(`[name='${input.prop("name")}'].deselect-radio-filters`);
if (deselectOption.length) {
if (deselectOption.prop("checked")) deselectOption.parent().hide();
else deselectOption.parent().show();
}
}
console.debug(`Filter applied '${key}' as '${uuid}'`)
loadTicketItems();
});
});
}
function getOrdinalSuffix(day) {
if (day >= 11 && day <= 13) {
return day + 'th';
} else {
switch (day % 10) {
case 1: return day + 'st';
case 2: return day + 'nd';
case 3: return day + 'rd';
default: return day + 'th';
}
}
}
function timestampToHumanDate(timestamp, wasYesterday) {
if (wasYesterday) {
var day = getOrdinalSuffix(timestamp.getDate());
var month = timestamp.toLocaleString('en-GB', { month: 'short' });
var year = timestamp.toLocaleString('en-GB', { year: 'numeric' });
var time = timestamp.toLocaleString('en-GB', { hour: 'numeric', minute: 'numeric' });
return time + ', ' + day + ' ' + month + ' ' + year;
}
var hours = timestamp.getUTCHours();
var minutes = timestamp.getUTCMinutes();
return hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0');
}
function updateFilterCounts(filterType, data) {
$("#filterSidebar .filter-" + filterType).each(function() {
var uuid = $(this).find("input[type=checkbox],input[type=radio]").val();
var count = data[filterType][uuid];
$(this).find(".badge").text(count);
});
}
function loadFilterCounts() {
$.ajax({
url: URL_FilterCounts,
type: "GET",
success: function(data) {
updateFilterCounts('priority', data);
updateFilterCounts('tags', data);
updateFilterCounts('department', data);
// $("#filterPriorityAll .badge").text(data.tickets);
// $("#filterDepartmentAll .badge").text(data.tickets);
// $("#ticketCounts .total").text(data.tickets);
},
error: function(data) {
console.error(JSON.stringify(data, null, 4))
}
});
}
function applyTicketClickFunction() {
$(".ticket-item").on("click", function(e) {
e.preventDefault();
if ($(this).hasClass("active")) {
return;
}
loadTicketContent($(this).data("uuid"));
$(".ticket-item").removeClass("active");
$(this).addClass("active");
$('.email-app').removeClass('side-active');
$('.email-content').toggleClass('open');
});
}
$(".back-to-mailbox").on("click", function(e) {
e.preventDefault();
$(".ticket-item.active").removeClass("active");
});
function reloadCurrentTicket() {
loadTicketContent($(".ticket-item.active").data("uuid"));
}
function changeTicket(next=true) {
var selectedTicket = $(".ticket-item.active");
var uuid;
if (!selectedTicket.length) selectedTicket = $(".ticket-item").first();
else if (next) selectedTicket = selectedTicket.next();
else selectedTicket = selectedTicket.prev();
$(".ticket-item").removeClass("active");
selectedTicket.addClass("active");
uuid = selectedTicket.data("uuid");
loadTicketContent(uuid);
if (!$('.email-content').hasClass('open')) {
$('.email-content').addClass('open');
}
}
function changeItemsPage(next) {
$("#ticketItemsNextPage").prop("disabled", true);
$("#ticketItemsPrevPage").prop("disabled", true);
var page = pagination.page;
if (next && pagination.next) page ++;
else if (!next && pagination.prev) page --;
else return;
loadTicketItems(page);
}
$("#strictTags").on("change", function() {
const strictTags = $(this).prop("checked");
if (strictTags) filters["strict-tags"] = strictTags;
else delete filters["strict-tags"];
loadTicketItems();
});
/**
* Loads the list of ticket items using the current filters.
*
* @function loadTicketItems
* @param {Number} page For pagination, an invalid page will result in an error.
*/
function loadTicketItems(page=1) {
if (global_loadingTickets) {
alert("Spam prevention\nStopped loadTicketItems because already loading.");
return;
}
global_loadingTickets = true;
updateItemsState("loading");
filters["page"] = page;
var fetchFilters = { ...filters };
fetchFilters["only_fields"] = "uuid,title,short_description,author,priority,tags,timestamp,is_edited,author__forename,author__surname,author__department,author__icon,display_datetime";
fetchTicketsPromise(fetchFilters).then((response) => {
// Update the counts to show how many tickets were found
$("#ticketCounts .current").text(response.results.length);
$("#ticketCounts .total").text(response.count);
// If there are no tickets
if (!response.count) {
updateItemsState("no-content");
return;
}
const ticketHasNextPage = typeof response.next === "string";
const ticketHasPrevPage = typeof response.previous === "string";
$("#ticketItemsNextPage").prop("disabled", !ticketHasNextPage);
$("#ticketItemsPrevPage").prop("disabled", !ticketHasPrevPage);
const pageNumber = filters["page"] || 1; // If we haven't tracked the page, it's safe to assume it's 1,
pagination = { // because we always track it when it changes.
"page": pageNumber,
"next": ticketHasNextPage,
"prev": ticketHasPrevPage
}
// Update the pagination count with the current page
$("#paginationCounts .current").text(pageNumber);
// If we are on page one (which has been normalised to be the case in many intances)
// We can use the amount of results to calculate the total amount of pages.
if (pageNumber === 1) {
$("#paginationCounts .total").text(Math.ceil(response.count / response.results.length));
}
// Iterate over and handle each ticket
response.results.forEach(function(ticket) {
$("#ticketsContainer .content").append(createTicketItem(ticket));
});
// Make tickets clickable
applyTicketClickFunction();
updateItemsState("content");
global_loadingTickets = false;
});
}
/**
* Returns a jquery object representing an element for a ticket item, constructed using the passed
* ticket and a predefined template.
*
* @function createTicketItem
* @param {Object} ticket An object representing a ticket.
* @return {jQuery} ticketElement to be appeneded as content.
*/
function createTicketItem(ticket) {
// Create a copy of the template using the ticket data
var template = $($("#ticketItemTemplate").html());
template.find(".ticket-item-author").text(`${ticket.author.forename} ${ticket.author.surname}`);
template.find(".ticket-item-datetime").text(ticket.display_datetime);
template.find(".ticket-item-title").text(ticket.title);
template.find(".ticket-item-desc").html(ticket.short_description);
template.find(".ticket-item-icon").attr("src", ticket.author.icon);
template.attr("data-uuid", ticket.uuid);
// Add tickets using the badge template
ticket.tags.forEach(function(tag) {
var tagTemplate = $($("#ticketContentBadgeTemplate").html());
tagTemplate.find(".ticket-content-badge-text").text(tag.title);
tagTemplate.css({ "color": tag.colour, "background-color": tag.backgroundcolour });
template.find(".ticket-item-tags").append(tagTemplate);
});
const priority = ticket.priority;
var priorityElem = template.find(".ticket-item-priority");
priorityElem.css("color", priority.colour);
priorityElem.css("background-color", priority.backgroundcolour);
priorityElem.attr("data-bs-title", priority.title + " Priority");
priorityElem.tooltip();
const department = ticket.author.department;
var departmentElem = template.find(".ticket-item-department");
if (department === null) {
departmentElem.hide();
}
else {
departmentElem.css("color", ticket.author.department.colour);
departmentElem.css("background-color", ticket.author.department.backgroundcolour);
departmentElem.attr("data-bs-title", ticket.author.department.title + " Department");
departmentElem.tooltip();
}
return template;
}
/**
* Load the content of a selected ticket.
*
* @function loadTicketContent
* @param {String} uuid A string representation of the ticket's UUID.
*/
function loadTicketContent(uuid) {
updateContentState("loading");
if (global_loadingTickets) {
console.debug("Spam prevention\nStopped loadTicketContent because already loading.");
return;
}
global_loadingTickets = true;
$("#ticketContent .content").empty();
fetchTicketsPromise({uuid: uuid}).then((response) => {
ticket = response.results[0];
$("#ticketContent .content").append(createTicketContent(ticket));
updateContentState("content");
global_loadingTickets = false;
});
}
/**
* Returns a jquery object representing an element for a ticket content, constructed using the
* passed ticket and a predefined template.
*
* @function createTicketItem
* @param {Object} ticket An object representing a ticket.
* @return {jQuery} ticketElement to be shown as content.
*/
function createTicketContent(ticket) {
// Create a copy of the template using the ticket data
var template = $($("#ticketContentTemplate").html());
template.find(".ticket-content-author").text(`${ticket.author.forename} ${ticket.author.surname}`);
template.find(".ticket-content-datetime").text(ticket.display_datetime);
template.find(".ticket-content-title").text(ticket.title);
template.find(".ticket-content-desc").html(ticket.description);
template.find(".ticket-content-icon").attr("src", ticket.author.icon);
console.debug(ticket.description);
ticket.tags.forEach(function(tag) {
var tagTemplate = $($("#ticketContentBadgeTemplate").html());
tagTemplate.find(".ticket-content-badge-text").text(tag.title);
tagTemplate.css({ "color": tag.colour, "background-color": tag.backgroundcolour });
template.find(".ticket-content-badges").append(tagTemplate);
});
return template;
}
function fetchTicketsPromise(queryFilters) {
return new Promise(function(resolve, reject) {
global_loadingTickets = true;
$.ajax({
url: URL_Tickets,
type: "GET",
dataType: "JSON",
data: $.param(queryFilters, true),
success: function(response) {
global_loadingTickets = false;
resolve(response);
},
error: function(response) {
global_loadingTickets = false;
if (response.status === 429) {
alert(`
HTTP ${response.status} - ${response.statusText}\n
${response.responseJSON.detail}
`);
}
reject(response);
}
});
});
}
// Prevent certain dropdowns from closing when the user clicks.
$(".dropdown-menu.prevent-click-close").on("click", function(e) {
e.stopPropagation();
});
function toggleComplexItems(hideComplex=null) {
if (hideComplex === null) {
hideComplex = !(localStorage.getItem("hideComplexTickets") === "true");
}
if (hideComplex) $("#ticketsContainer").removeClass("complex-items");
else $("#ticketsContainer").addClass("complex-items");
localStorage.setItem("hideComplexTickets", hideComplex);
}

View File

@ -1,4 +1,5 @@
{% extends "layouts/base-authentication.html" %}
{% load static %}
{% block title %} Sign IN {% endblock title %}
@ -8,10 +9,10 @@
{% block content %}
<div class="peers ai-s fxw-nw h-100vh">
<div class="d-n@sm- peer peer-greed h-100 pos-r bgr-n bgpX-c bgpY-c bgsz-cv" style='background-image: url("{{ ASSETS_ROOT }}/images/bg.jpg")'>
<div class="d-n@sm- peer peer-greed h-100 pos-r bgr-n bgpX-c bgpY-c bgsz-cv" style='background-image: url("{% static '/images/bg.jpg' %}")'>
<div class="pos-a centerXY">
<div class="bgc-white bdrs-50p pos-r" style='width: 120px; height: 120px;'>
<img class="pos-a centerXY" src="{{ ASSETS_ROOT }}/images/logo.png" alt="">
<img class="pos-a centerXY" src="{% static '/images/logo.png' %}" alt="">
</div>
</div>
</div>

View File

@ -1,4 +1,5 @@
{% extends "layouts/base-authentication.html" %}
{% load static %}
{% block title %} Register {% endblock title %}
@ -8,10 +9,10 @@
{% block content %}
<div class="peers ai-s fxw-nw h-100vh">
<div class="peer peer-greed h-100 pos-r bgr-n bgpX-c bgpY-c bgsz-cv" style='background-image: url("{{ ASSETS_ROOT }}/images/bg.jpg")'>
<div class="peer peer-greed h-100 pos-r bgr-n bgpX-c bgpY-c bgsz-cv" style='background-image: url("{% static '/images/bg.jpg' %}")'>
<div class="pos-a centerXY">
<div class="bgc-white bdrs-50p pos-r" style='width: 120px; height: 120px;'>
<img class="pos-a centerXY" src="{{ ASSETS_ROOT }}/images/logo.png" alt="">
<img class="pos-a centerXY" src="{% static '/images/logo.png' %}" alt="">
</div>
</div>
</div>

View File

@ -1,4 +1,5 @@
{% extends "layouts/base-error.html" %}
{% load static %}
{% block title %} Error 404 {% endblock title %}
@ -9,7 +10,7 @@
<div class='pos-a t-0 l-0 bgc-white w-100 h-100 d-f fxd-r fxw-w ai-c jc-c pos-r p-30'>
<div class='mR-60'>
<img alt='#' src='{{ ASSETS_ROOT }}/images/404.png' />
<img alt='#' src="{% static '/images/404.png' %}" />
</div>
<div class='d-f jc-c fxd-c'>

View File

@ -1,4 +1,5 @@
{% extends "layouts/base-error.html" %}
{% load static %}
{% block title %} Error 500 {% endblock title %}
@ -9,7 +10,7 @@
<div class='pos-a t-0 l-0 bgc-white w-100 h-100 d-f fxd-r fxw-w ai-c jc-c pos-r p-30'>
<div class='mR-60'>
<img alt='#' src='{{ ASSETS_ROOT }}/images/500.png' />
<img alt='#' src="{% static '/images/500.png' %}" />
</div>
<div class='d-f jc-c fxd-c'>
@ -17,7 +18,7 @@
<h3 class='mB-10 fsz-lg c-grey-900 tt-c'>Internal server error</h3>
<p class='mB-30 fsz-def c-grey-700'>Something goes wrong with our servers, please try again later.</p>
<div>
<a href="index.html" type='primary' class='btn btn-primary'>Go to Home</a>
<a href="/" type='primary' class='btn btn-primary'>Go to Home</a>
</div>
</div>
</div>

View File

@ -1,4 +1,5 @@
{% extends "layouts/base-authentication.html" %}
{% load static %}
{% block title %} Sign IN {% endblock title %}
@ -8,10 +9,10 @@
{% block content %}
<div class="peers ai-s fxw-nw h-100vh">
<div class="d-n@sm- peer peer-greed h-100 pos-r bgr-n bgpX-c bgpY-c bgsz-cv" style='background-image: url("{{ ASSETS_ROOT }}/images/bg.jpg")'>
<div class="d-n@sm- peer peer-greed h-100 pos-r bgr-n bgpX-c bgpY-c bgsz-cv" style='background-image: url("{% static '/images/bg.jpg' %}")'>
<div class="pos-a centerXY">
<div class="bgc-white bdrs-50p pos-r" style='width: 120px; height: 120px;'>
<img class="pos-a centerXY" src="{{ ASSETS_ROOT }}/images/logo.png" alt="">
<img class="pos-a centerXY" src="{% static '/images/logo.png' %}" alt="">
</div>
</div>
</div>

View File

@ -1,4 +1,5 @@
{% extends "layouts/base-authentication.html" %}
{% load static %}
{% block title %} Register {% endblock title %}
@ -8,10 +9,10 @@
{% block content %}
<div class="peers ai-s fxw-nw h-100vh">
<div class="peer peer-greed h-100 pos-r bgr-n bgpX-c bgpY-c bgsz-cv" style='background-image: url("{{ ASSETS_ROOT }}/images/bg.jpg")'>
<div class="peer peer-greed h-100 pos-r bgr-n bgpX-c bgpY-c bgsz-cv" style='background-image: url("{% static '/images/bg.jpg' %}")'>
<div class="pos-a centerXY">
<div class="bgc-white bdrs-50p pos-r" style='width: 120px; height: 120px;'>
<img class="pos-a centerXY" src="{{ ASSETS_ROOT }}/images/logo.png" alt="">
<img class="pos-a centerXY" src="{% static '/images/logo.png' %}" alt="">
</div>
</div>
</div>

View File

@ -5,322 +5,340 @@
<!-- Specific CSS goes HERE -->
{% block stylesheets %}
<link rel="stylesheet" href="{{ ASSETS_ROOT }}/css/select2-bootstrap.min.css">
<link rel="stylesheet" href="{% static '/css/select2-bootstrap.min.css' %}">
{% endblock stylesheets %}
{% block content %}
<!-- ### $App Screen Content ### -->
<main class='main-content bgc-grey-100'>
<main class='main-content'>
<div id='mainContent'>
<div class="full-container">
<div class="full-container" style="overflow: hidden;">
<div class="email-app">
<div class="email-side-nav remain-height ov-h">
<div class="h-100 layers">
<div class="p-20 bgc-grey-100 layer w-100">
<div class="p-20 bg-body-tertiary layer w-100">
<button type="button" class="btn btn-danger c-white w-100" data-bs-toggle="modal" data-bs-target="#ticketModal">New Ticket</button>
</div>
<div class="scrollable pos-r bdT layer w-100 fxg-1">
<ul class="p-20 nav flex-column">
<li class="nav-item">
<h6>Filters</h6>
<div class="scrollable pos-r ov-h bdT layer w-100 fxg-1 bg-body">
<ul id="filterSidebar" class="p-20 nav flex-column">
{% if priorities %}
<li class="nav-item mT-10 filter-priority">
<h6 class="peers ai-c jc-sb mb-2 px-3">
<span class="peer-greed">Priorities</span>
<label for="filterPriority-all" class="nav-link text-reset actived p-0 cur-p" style="display: none">
<input type="radio" id="filterPriority-all" name="filterPriorities" class="btn-check deselect-radio-filters" checked="checked" value="all">
<span class="text-body badge small bg-none border-none py-0">
<i class="ti-close"></i>
</span>
</label>
</h6>
</li>
{% if priorities %}
<li class="nav-item px-3 mt-3">
<h6 class="small">Priority</h6>
{% for priority in priorities %}
<div class="form-check mb-2">
<input type="checkbox" id="priority-{{ priority.id }}" class="form-check-input me-3">
<label for="priority-{{ priority.id }}" class="form-check-label">
<span class="badge rounded-pill" style="color: {{ priority.colour }}; background-color: {{ priority.backgroundcolour }};">
{{ priority.title }}
<i class="ti-control-record ms-auto"></i>
</span>
</label>
</div>
{% endfor %}
</li>
{% endif %}
{% for priority in priorities %}
{% if departments %}
<li class="nav-item px-3 mt-3">
<h6 class="small">Department</h6>
{% for department in departments %}
<div class="form-check mb-2">
<input type="checkbox" id="department-{{ department.id }}" class="form-check-input me-2">
<label for="department-{{ department.id }}" class="form-check-label d-flex jc-sb">
{{ department.title }}
<i class="{{ department.icon }} ms-auto"></i>
</label>
<li class="nav-item filter-priority">
<label for="filterPriority-{{ priority.uuid }}" class="nav-link text-reset actived">
<div class="peers ai-c jc-sb">
<div class="peer peer-greed">
<input type="radio" id="filterPriority-{{ priority.uuid }}" name="filterPriorities" class="form-check-input me-2" value="{{ priority.uuid }}">
<span>{{ priority.title }}</span>
</div>
<div class="peer">
<span class="badge rounded-pill" style="color: {{ priority.colour }}; background-color: {{ priority.backgroundcolour }};">0</span>
</div>
</div>
{% endfor %}
</label>
</li>
{% endif %}
{% if tags %}
<li class="nav-item px-3 mt-3">
<h6 class="small">Tags</h6>
{% for tag in tags %}
<div class="form-check mb-2">
<input type="checkbox" id="tag-{{ tag.id }}" class="form-check-input me-3">
<label for="tag-{{ tag.id }}" class="form-check-label">
<span class="badge rounded-pill" style="color: {{ tag.colour }}; background-color: {{ tag.backgroundcolour }};">
{{ tag.title }}
<i class="ti-tag ms-auto"></i>
</span>
</label>
{% endfor %}
{% endif %}
<li class="nav-item">
<hr class="mY-30 border-secondary">
</li>
{% if tags %}
<li class="nav-item">
<h6 class="peers ai-c jc-sb mb-2 px-3">
<span class="peer-greed">Tags</span>
<div class="peer dropdown">
<span class="text-body badge small bg-none border-none py-0 cur-p" data-bs-toggle="dropdown">
<i class="ti-more-alt"></i>
</span>
<div class="dropdown-menu prevent-click-close mT-5">
<li class="px-3 py-2">
<label for="strictTags" class="form-check-label small mb-2 fw-normal">Only show tickets matching all selected tags?</label>
<div class="form-switch flex-wrap">
<input type="checkbox" name="strictTags" id="strictTags" class="form-check-input" checked="checked">
</div>
</li>
</div>
</div>
</h6>
</li>
{% for tag in tags %}
<li class="nav-item filter-tags">
<label for="filterTag-{{ tag.uuid }}" class="nav-link text-reset actived">
<div class="peers ai-c jc-sb">
<div class="peer peer-greed">
<input type="checkbox" id="filterTag-{{ tag.uuid }}" class="form-check-input me-2" value="{{ tag.uuid }}">
<span>{{ tag.title }}</span>
</div>
<div class="peer">
<span class="badge rounded-pill" style="color: {{ tag.colour }}; background-color: {{ tag.backgroundcolour }};">0</span>
</div>
</div>
{% endfor %}
</label>
</li>
{% endif %}
<!--
<li class="nav-item mt-5">
<a href="javascript:void(0)" class="nav-link c-grey-800 cH-blue-500 actived">
<div class="peers ai-c jc-sb">
<div class="peer peer-greed">
<i class="mR-10 ti-email"></i>
<span>Inbox</span>
</div>
<div class="peer">
<span class="badge rounded-pill bgc-deep-purple-50 c-deep-purple-700">+99</span>
</div>
</div>
</a>
</li> -->
<!--<li class="nav-item">
<a href="" class="nav-link c-grey-800 cH-blue-500">
<div class="peers ai-c jc-sb">
<div class="peer peer-greed">
<i class="mR-10 ti-share"></i>
<span>Sent</span>
</div>
<div class="peer">
<span class="badge rounded-pill bgc-green-50 c-green-700">12</span>
</div>
</div>
</a>
</li>
{% endfor %}
{% endif %}
<li class="nav-item">
<a href="" class="nav-link c-grey-800 cH-blue-500">
<div class="peers ai-c jc-sb">
<div class="peer peer-greed">
<i class="mR-10 ti-star"></i>
<span>Important</span>
</div>
<div class="peer">
<span class="badge rounded-pill bgc-blue-50 c-blue-700">3</span>
</div>
</div>
</a>
<hr class="mY-30 border-secondary">
</li>
<li class="nav-item">
<a href="" class="nav-link c-grey-800 cH-blue-500">
<div class="peers ai-c jc-sb">
<div class="peer peer-greed">
<i class="mR-10 ti-file"></i>
<span>Drafts</span>
</div>
<div class="peer">
<span class="badge rounded-pill bgc-amber-50 c-amber-700">5</span>
</div>
</div>
</a>
</li>
<li class="nav-item">
<a href="" class="nav-link c-grey-800 cH-blue-500">
<div class="peers ai-c jc-sb">
<div class="peer peer-greed">
<i class="mR-10 ti-alert"></i>
<span>Spam</span>
</div>
<div class="peer">
<span class="badge rounded-pill bgc-red-50 c-red-700">1</span>
</div>
</div>
</a>
</li>
<li class="nav-item">
<a href="" class="nav-link c-grey-800 cH-blue-500">
<div class="peers ai-c jc-sb">
<div class="peer peer-greed">
<i class="mR-10 ti-trash"></i>
<span>Trash</span>
</div>
<div class="peer">
<span class="badge rounded-pill bgc-red-50 c-red-700">+99</span>
</div>
</div>
</a>
</li> -->
{% if departments %}
<li id="filterDepartmentAll" class="nav-item filter-department">
<h6 class="peers ai-c jc-sb mb-2 px-3">
<span class="peer-greed">Departments</span>
<label for="filterDepartment-all" class="nav-link text-reset actived p-0 cur-p" style="display: none">
<input type="radio" id="filterDepartment-all" name="filterDepartment" class="btn-check deselect-radio-filters" checked="checked" value="all">
<span class="text-body badge small bg-none border-none py-0">
<i class="ti-close"></i>
</span>
</label>
</h6>
</li>
{% for department in departments %}
<li class="nav-item filter-department">
<label for="filterDepartment-{{ department.uuid }}" class="nav-link text-reset actived">
<div class="peers ai-c jc-sb">
<div class="peer peer-greed">
<input type="radio" id="filterDepartment-{{ department.uuid }}" name="filterDepartment" class="form-check-input me-2" value="{{ department.uuid }}">
<span>{{ department.title }}</span>
</div>
<div class="peer">
<span class="badge rounded-pill" style="color: {{ department.colour }}; background-color: {{ department.backgroundcolour }};">0</span>
</div>
</div>
</label>
</li>
{% endfor %}
{% endif %}
</ul>
</div>
</div>
</div>
<div class="email-wrapper row remain-height bgc-white ov-h">
<div class="email-wrapper row remain-height bg-body">
<div class="email-list h-100 layers">
<div class="layer w-100">
<div class="bgc-grey-100 peers ai-c p-20 fxw-nw">
<div class="peer me-auto">
<div class="btn-group" role="group">
<button type="button" class="email-side-toggle d-n@md+ btn bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-menu"></i>
</button>
<button type="button" class="btn bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-folder"></i>
</button>
<button type="button" class="btn bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-tag"></i>
</button>
<div class="bg-body-tertiary peers ai-c p-20 fxw-nw">
<div class="peer me-2">
<div class="btn-group" role="group">
<button id="btnGroupDrop1" type="button" class="btn cur-p bgc-white no-after dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="ti-more-alt"></i>
<button type="button" class="email-side-toggle d-n@md+ btn bg-body bdrs-2 mR-3">
<i class="ti-menu"></i>
</button>
<!-- <button type="button" class="btn bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-tag"></i>
</button> -->
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript:loadTicketItems();">
<i class="ti-reload"></i>
</button>
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript: toggleComplexItems();">
<i class="ti-layout-media-left"></i>
</button>
<ul class="dropdown-menu fsz-sm" aria-labelledby="btnGroupDrop1">
<li>
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
<i class="ti-trash mR-10"></i>
<span>Delete</span>
</a>
</li>
<li>
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
<i class="ti-alert mR-10"></i>
<span>Mark as Spam</span>
</a>
</li>
<li>
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
<i class="ti-star mR-10"></i>
<span>Star</span>
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="peer">
<div class="btn-group" role="group">
<button type="button" class="fsz-xs btn bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-angle-left"></i>
</button>
<button type="button" class="fsz-xs btn bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-angle-right"></i>
</button>
<!-- <div class="peer me-2">
<div class="btn-group" role="group">
</div>
</div> -->
<div class="peer ms-auto">
<div class="btn-group bg-body mX-3" role="group">
<button type="button" id="ticketItemsPrevPage" class="btn bg-body bdrs-2" onclick="javascript: changeItemsPage(false);" disabled>
<i class="ti-angle-left"></i>
</button>
<div id="paginationCounts" class="bg-body pX-3 small d-flex ai-c">
<span class="current">-</span>/<span class="total">-</span>
</div>
<button type="button" id="ticketItemsNextPage" class="btn bg-body bdrs-2" onclick="javascript: changeItemsPage(true);" disabled>
<i class="ti-angle-right align-center"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<div class="layer w-100">
<div class="bdT bdB">
<input type="text" class="form-control m-0 bdw-0 pY-15 pX-20 bdrs-0" placeholder="Search...">
<div class="bdT bdB peers d-flex flex-nowrap">
<label for="searchTickets" class="peer my-auto mX-15">
<i class="ti-search"></i>
</label>
<input type="text" id="searchTickets" class="form-control m-0 bdw-0 pY-15 pR-20 pL-0 bdrs-0 peer-greed shadow-none" placeholder="Search...">
<label for="searchTickets" id="ticketCounts" class="peer my-auto mX-15 small">
<span class="current"></span>/<span class="total"></span>
</label>
</div>
</div>
<div id="ticketsContainer" class="layer w-100 fxg-1 scrollable pos-r"></div>
<div id="ticketsContainer" class="layer w-100 fxg-1 scrollable pos-r ov-h">
<div class="content"></div>
<div class="none-found p-20" style="display: none;">
<div class="pos-a top-50 start-50 translate-middle text-center">
<div class="fs-1 fw-bolder">404</div>
<div class="fs-4 fw-bold text-body-tertiary">No Tickets Found</div>
</div>
</div>
<div class="loading bg-body-tertiary h-100" style="display: none;">
{% for i in "x"|rjust:"3" %}
<div class="email-list-item peers fxw-nw p-20 bdB placeholder-glow" >
<div class="peer mR-10">
<span class="placeholder w-2r h-2r bdrs-50p me-2"></span>
</div>
<div class="peer peer-greed ov-h">
<div class="peers ai-c">
<div class="peer peer-greed">
<span class="placeholder col-5 rounded"></span>
</div>
<div class="peer peer-greed d-flex jc-fe">
<span class="placeholder col-4 rounded"></span>
</div>
</div>
<span class="placeholder rounded col-7 mT-10"></span>
<div class="row">
<span class="col-1"></span>
<span class="placeholder rounded col-7 mT-10"></span>
</div>
</div>
</div>
<div class="email-list-item peers fxw-nw p-20 bdB placeholder-glow" >
<div class="peer mR-10">
<span class="placeholder w-2r h-2r bdrs-50p me-2"></span>
</div>
<div class="peer peer-greed ov-h">
<div class="peers ai-c">
<div class="peer peer-greed">
<span class="placeholder col-6 rounded"></span>
</div>
<div class="peer peer-greed d-flex jc-fe">
<span class="placeholder col-4 rounded"></span>
</div>
</div>
<span class="placeholder rounded col-8 mT-10"></span>
<span class="placeholder rounded col-7 mT-10"></span>
</div>
</div>
<div class="email-list-item peers fxw-nw p-20 bdB placeholder-glow" >
<div class="peer mR-10">
<span class="placeholder w-2r h-2r bdrs-50p me-2"></span>
</div>
<div class="peer peer-greed ov-h">
<div class="peers ai-c">
<div class="peer peer-greed">
<span class="placeholder col-5 rounded"></span>
</div>
<div class="peer peer-greed d-flex jc-fe">
<span class="placeholder col-4 rounded"></span>
</div>
</div>
<div class="row">
<span class="col-1"></span>
<span class="placeholder rounded col-7 mT-10"></span>
</div>
<span class="placeholder rounded col-7 mT-10"></span>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
<div class="email-content h-100">
<div class="email-content h-100 bg-body">
<div class="h-100 scrollable pos-r">
<div class="bgc-grey-100 peers ai-c jc-sb p-20 fxw-nw d-n@md+">
<div class="peer">
<div class="btn-group" role="group">
<button type="button" class="back-to-mailbox btn bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-angle-left"></i>
</button>
<button type="button" class="btn bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-folder"></i>
</button>
<button type="button" class="btn bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-tag"></i>
</button>
<div class="btn-group" role="group">
<button id="btnGroupDrop1" type="button" class="btn cur-p bgc-white no-after dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="ti-more-alt"></i>
</button>
<ul class="dropdown-menu fsz-sm" aria-labelledby="btnGroupDrop1">
<li>
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
<i class="ti-trash mR-10"></i>
<span>Delete</span>
</a>
</li>
<li>
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
<i class="ti-alert mR-10"></i>
<span>Mark as Spam</span>
</a>
</li>
<li>
<a href="" class="d-b td-n pY-5 pX-10 bgcH-grey-100 c-grey-700">
<i class="ti-star mR-10"></i>
<span>Star</span>
</a>
</li>
</ul>
</div>
</div>
</div>
<div class="peer">
<div class="btn-group" role="group">
<button type="button" class="fsz-xs btn bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-angle-left"></i>
</button>
<button type="button" class="fsz-xs btn bgc-white bdrs-2 mR-3 cur-p">
<i class="ti-angle-right"></i>
</button>
</div>
</div>
</div>
<div class="email-content-wrapper">
<!-- Header -->
<div class="peers ai-c jc-sb pX-40 pY-30">
<div class="peers peer-greed">
<div class="peer mR-20">
<img id="ticketAuthorImg" class="bdrs-50p w-3r h-3r" alt="" src="" style="display: none; object-fit: cover;">
</div>
<div class="bg-body-tertiary peers ai-c jc-sb p-20 fxw-nw d-n@md+">
<div class="peer">
<small id="ticketTimestamp"></small>
<h5 id="ticketAuthor" class="c-grey-900 mB-5"></h5>
<div id="ticketBadges" style="display: none;"></div>
</div>
<div class="btn-group" role="group">
<button type="button" class="back-to-mailbox btn bg-body bdrs-2 mR-3">
<i class="ti-angle-left"></i>
</button>
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript:reloadCurrentTicket();">
<i class="ti-reload"></i>
</button>
<button type="button" class="btn bg-body bdrs-2 mR-3">
<i class="ti-more-alt"></i>
</button>
</div>
</div>
<div class="peer">
<div class="btn-group" role="group">
<button id="btnGroupDrop2" class="btn btn-danger c-white bdrs-50p p-15 lh-0" style="display: none;" type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="ti-menu"></i>
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript:changeTicket(false);">
<i class="ti-angle-left"></i>
</button>
<button type="button" class="btn bg-body bdrs-2 mR-3" onclick="javascript:changeTicket(true);">
<i class="ti-angle-right"></i>
</button>
<ul class="dropdown-menu fsz-sm" aria-labelledby="btnGroupDrop2">
test
</ul>
</div>
</div>
</div>
<div id="ticketContent" class="email-content-wrapper">
<div class="content"></div>
<div class="loading" style="display: none;">
<!-- Content -->
<div class="bdT pX-40 pY-30">
<!-- Header -->
<div class="ticket-content placeholder-glow">
<div class="peers ai-c jc-sb pX-40 pY-30">
<div class="peers peer-greed">
<div class="peer mR-20">
<span class="ticket-content-icon placeholder me-2"></span>
</div>
<div class="peer-greed">
<div >
<span class="placeholder rounded col-4"></span>
</div>
<div class="mY-5">
<span class="placeholder rounded col-5"></span>
</div>
<div class="row g-0 ">
{% for i in "x"|rjust:"12" %}
<div class="col pe-3">
<div class="placeholder rounded w-100"></div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
<h4 id="ticketTitle"></h4>
<div id="ticketDesc"></div>
<!-- <h4>Title of this email goes here</h4>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam
</p> -->
</div>
<!-- Content -->
<div class="bdT pX-40 pY-30">
<span class="placeholder rounded col-6 mB-30"></span>
<div class="row g-3">
<div class="placeholder rounded col-12 px-3"></div>
<div class="placeholder rounded col-7"></div>
<div class="col-1"></div>
<div class="placeholder rounded col-4"></div>
<div class="placeholder rounded col-3"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
@ -349,7 +367,7 @@
</div>
<div class="mb-3">
<label for="newDesc" class="form-label">Description</label>
<div id="newDesc" class="form-control"></div>
<div id="newDesc" class="form-control"></div>
<small class="text-muted">Describe your issue in detail here.</small>
<!-- class="form-control" -->
</div>
@ -357,16 +375,16 @@
<label for="newPriority" class="form-label">Priority</label>
<select name="newPriority" id="newPriority" class="select-2">
{% for priority in priorities %}
<option value="{{ priority.id }}">{{ priority.title }}</option>
<option value="{{ priority.uuid }}">{{ priority.title }}</option>
{% endfor %}
</select>
<small class="text-muted">How important is this ticket?</small>
</div>
<div class="mb-3">
<label for="newTagss" class="form-label">Tags</label>
<label for="newTags" class="form-label">Tags</label>
<select name="newTags" id="newTags" class="select-2" multiple="multiple">
{% for tag in tags %}
<option value="{{ tag.id }}">{{ tag.title }}</option>
<option value="{{ tag.uuid }}">{{ tag.title }}</option>
{% endfor %}
</select>
<small class="text-muted">Use tags to categorize this ticket.</small>
@ -383,208 +401,84 @@
{% endblock content %}
<!-- Specific Page JS goes HERE -->
{% block javascripts %}
<script>
var displayedTicketID = -1;
$(document).ready(function() {
// $(".email-list-item").on("click", function() {
// displayTicket(this);
// });
ClassicEditor
.create( document.getElementById("newDesc"), {})
.catch( error => {
console.error(error)
});
loadAllTickets();
});
$("#ticketModal form").on("submit", function(event) {
event.preventDefault();
$.ajax({
url: "{% url 'ticket-new' %}",
type: "POST",
dataType: "json",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
}
});
});
function getOrdinalSuffix(day) {
if (day >= 11 && day <= 13) {
return day + 'th';
} else {
switch (day % 10) {
case 1: return day + 'st';
case 2: return day + 'nd';
case 3: return day + 'rd';
default: return day + 'th';
}
}
}
function loadAllTickets() {
$("#ticketsContainer").empty();
$.ajax({
url: "{% url 'ticket-getmany' %}",
type: "POST",
dataType: "json",
data: {
csrfmiddlewaretoken: "{{ csrf_token }}",
filters: {}
},
success: function(data) {
console.log(JSON.stringify(data, null, 4))
data.tickets.forEach(function(ticket) {
var timestamp = new Date(ticket.timestamp);
var formattedTime;
if (ticket.was_yesterday) {
var day = getOrdinalSuffix(timestamp.getDate());
var month = timestamp.toLocaleString('en-GB', { month: 'short' });
var year = timestamp.toLocaleString('en-GB', { year: 'numeric' });
var time = timestamp.toLocaleString('en-GB', { hour: 'numeric', minute: 'numeric' });
// Formatting the final result
var formattedTime = time + ', ' + day + ' ' + month + ' ' + year;
}
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";
}
var item = $(`
<div class="email-list-item peers fxw-nw p-20 bdB bgcH-grey-100 cur-p" data-ticket-id="${ticket.id}" data-author-icon="${ticket.author.icon}">
<div class="peer mR-10">
<img src="${ticket.author.icon}" alt="" class="w-2r h-2r bdrs-50p me-2" style="object-fit: cover;">
</div>
<div class="peer peer-greed ov-h">
<div class="peers ai-c">
<div class="peer peer-greed">
<h6 class="ticket-author">${ticket.author.forename} ${ticket.author.surname}</h6>
</div>
<div class="peer">
<small class="ticket-timestamp">${formattedTime}</small>
</div>
</div>
<h5 class="fsz-def tt-c c-grey-900 ticket-title">${ticket.title}</h5>
<span class="whs-nw w-100 ov-h tov-e d-b ticket-desc">${ticket.description}</span>
</div>
</div>
`);
$("#ticketsContainer").append(item);
});
$(".email-list-item").on("click", function() {
displayTicket(this);
});
},
error: function(data) {
alert(JSON.stringify(data, null, 4))
}
});
}
function displayTicket(ticketElement) {
ticket = $(ticketElement);
ticketID = ticket.data("ticket-id");
$(".back-to-mailbox").off("click").on("click", function(event) {
event.preventDefault();
$('.email-content').toggleClass('open');
displayTicket(ticketElement);
});
$("#ticketTitle").text("")
$("#ticketDesc").empty();
$("#ticketAuthor").text("");
$("#ticketAuthorImg").hide();
$("#ticketAuthorImg").prop("src", "");
$("#ticketTimestamp").text("");
$("#btnGroupDrop2").hide();
$("#ticketBadges").empty().hide();
if (displayedTicketID === ticketID) {
displayedTicketID = -1;
return;
}
displayedTicketID = ticketID;
$.ajax({
url: `{% url 'ticket-getone' %}`,
type: 'POST',
dataType: 'json',
data: {
csrfmiddlewaretoken: '{{ csrf_token }}',
ticket_id: ticketID
},
success: function (data) {
console.log(JSON.stringify(data, null, 4));
var ticket = data.ticket;
var author = ticket.author;
var department = author.department;
var priority = ticket.priority;
$("#ticketTitle").text(ticket.title);
$("#ticketDesc").append($(`<div class="w-100">${ticket.description}</div>`));
$("#ticketAuthor").text(`${author.forename} ${author.surname}`);
$("#ticketAuthorImg").show();
$("#ticketAuthorImg").prop("src", author.icon);
$("#btnGroupDrop2").show();
$("#ticketBadges").show();
$("#ticketBadges").append($(`<div class="badge me-1" style="color: ${priority.colour}; background-color: ${priority.backgroundcolour};">${priority.title} Priority <i class="ti-control-record "></i></div>`));
if (department != null) {
$("#ticketBadges").append($(`<div class="badge bgc-deep-purple-500 me-1">${department.title}</div>`));
}
ticket.tags.forEach(function(tag) {
$("#ticketBadges").append($(`<div class="badge me-1" style="color: ${tag.colour}; background-color: ${tag.backgroundcolour};">${tag.title} <i class="ti-tag"></i></div>`));
});
// timestamp
var timestamp = new Date(ticket.timestamp);
var formattedTime;
if (ticket.was_yesterday) {
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));
}
});
}
<!-- Ticket Item Template -->
<script id="ticketItemTemplate" type="text/template">
<div class="ticket-item fxw-nw bdB peers fxw-nw p-20 w-100" data-uuid="-1">
<div class="ticket-item-complex peer mR-20 d-flex flex-column align-items-center align-self-stretch pos-r">
<img src="" alt="" class="ticket-item-icon">
<div class="mt-auto">
<div class="ticket-item-department badge rounded mb-2" data-bs-toggle="tooltip">
<i class="fa fa-users"></i>
</div>
<div class="ticket-item-priority badge rounded" data-bs-toggle="tooltip">
<i class="fa fa-folder"></i>
</div>
</div>
</div>
<div class="peer peer-greed ov-h">
<div class="peers ai-c mb-2">
<div class="peer peer-greed">
<h6 class="ticket-item-author"></h6>
</div>
<div class="peer">
<small class="ticket-item-datetime"></small>
</div>
</div>
<h5 class="ticket-item-title mb-0"></h5>
<div class="ticket-item-desc mt-2"></div>
<div class="peers">
<div class="ticket-item-tags peer d-flex flex-wrap mw-100"></div>
</div>
</div>
</div>
</script>
<!-- Ticket Content Template -->
<script id="ticketContentTemplate" type="text/template">
<!-- Header -->
<div class="ticket-content">
<div class="peers ai-c jc-sb pX-40 pY-30">
<div class="peers peer-greed">
<div class="peer mR-20">
<img class="ticket-content-icon" src="" alt="">
</div>
<div class="peer">
<!-- <small class="ticket-content-datetime"></small> -->
<h5 class="ticket-content-author mb-0"></h5>
<div class="peers mt-2">
<div class="peer badge bgc-orange-100 c-orange-700">
<i class="fa fa-users"></i>
</div>
</div>
<div class="ticket-content-badges"></div>
</div>
</div>
</div>
<!-- Content -->
<div class="bdT pX-40 pY-30">
<h4 class="ticket-content-title"></h4>
<div class="ticket-content-desc w-100"></div>
</div>
</div>
</script>
<!-- Ticket Content Badge Template -->
<script id="ticketContentBadgeTemplate" type=text/template>
<div class="ticket-content-badge badge rounded mt-2 me-2">
<span class="ticket-content-badge-text"></span>
<i class="ticket-content-badge-icon"></i>
</div>
</script>
<!-- Define Variables -->
<script>
const URL_Tickets = "{% url 'api:tickets' %}";
const URL_NewTicket = "{% url 'ticket-new' %}";
const URL_FilterCounts = "{% url 'api:filter-counts' %}";
const CSRFMiddlewareToken = "{{ csrf_token }}";
const CurrentUserID = "{{ request.user.uuid }}";
</script>
<script src="{% static '/js/tickets.js' %}"></script>
{% endblock javascripts %}

View File

@ -1,4 +1,4 @@
<footer class="bdT ta-c p-30 lh-0 fsz-sm c-grey-600">
<footer class="bdT ta-c p-30 lh-0 fsz-sm c-grey-600 bg-body">
<span>
&copy; Designed by Colorlib.
</span>

View File

@ -1,4 +1,4 @@
<div class="header navbar">
<div class="header navbar bg-body">
<div class="header-container">
<ul class="nav-left">
<li>
@ -17,6 +17,11 @@
</li>
</ul>
<ul class="nav-right">
<li>
<a href="" id="themeToggle" data-bs-toggle="dropdown">
<i class="ti-shine"></i>
</a>
</li>
<li class="notifications dropdown">
<!-- <span class="counter bgc-red">3</span> -->
<a href="" class="dropdown-toggle no-after" data-bs-toggle="dropdown">
@ -106,7 +111,7 @@
<img class="w-2r h-2r bdrs-50p" src="{{ request.user.icon.url }}" style="object-fit: cover;" alt="">
</div>
<div class="peer">
<span class="fsz-sm c-grey-900">{{ request.user.formal_fullname }}</span>
<span class="fsz-sm">{{ request.user.formal_fullname }}</span>
</div>
</a>
<ul class="dropdown-menu fsz-sm">

View File

@ -1,9 +1,17 @@
<script src="{{ ASSETS_ROOT }}/js/jquery-3.6.0.min.js"></script>
{% load static %}
<script src="{{ ASSETS_ROOT }}/js/jquery.dataTables.min.js"></script>
<script src="{% static '/js/jquery-3.6.0.min.js' %}"></script>
<script src="{{ ASSETS_ROOT }}/js/ckeditor.js"></script>
<script src="{% static '/js/jquery.dataTables.min.js' %}"></script>
<script src="{{ ASSETS_ROOT }}/js/select2.min.js"></script>
<script src="{% static '/js/ckeditor.js' %}"></script>
<script src="{{ ASSETS_ROOT }}/js/index.js"></script>
<script src="{% static '/js/select2.min.js' %}"></script>
<script src="{% static '/js/perfectscrollbar.js' %}"></script>
<script src="{% static '/js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static '/js/index.js' %}"></script>
<script src="{% static '/js/base.js' %}"></script>

View File

@ -1,19 +1,19 @@
<div class="sidebar">
{% load static %}
<div class="sidebar bg-body">
<div class="sidebar-inner">
<!-- ### $Sidebar Header ### -->
<div class="sidebar-logo">
<div class="peers ai-c fxw-nw">
<div class="peer peer-greed">
<a class="sidebar-link td-n" href="/index.html">
<a class="sidebar-link td-n" href="/">
<div class="peers ai-c fxw-nw">
<div class="peer">
<div class="logo">
<img src="{{ ASSETS_ROOT }}/images/logo.png" alt="">
<img src="{% static '/images/logo.png' %}" alt="">
</div>
</div>
<div class="peer peer-greed">
<h5 class="lh-1 mB-0 logo-text">Adminator</h5>
<h5 class="lh-1 mB-0 text-body-tertiary">Adminator</h5>
</div>
</div>
</a>
@ -35,7 +35,7 @@
<span class="icon-holder">
<i class="c-blue-500 ti-home"></i>
</span>
<span class="title">New Dashboard</span>
<span class="title">Admin Dashboard</span>
<!-- ti-comment-alt -->
</a>
</li>
@ -47,10 +47,10 @@
<span class="title">Tickets</span>
</a>
</li>
<li class="nav-item">
<!-- <li class="nav-item">
<hr class="bgc-grey-500">
</li>
<li class="nav-item">
</li> -->
<!-- <li class="nav-item">
<a class="sidebar-link" href="/index.html">
<span class="icon-holder">
<i class="c-blue-500 ti-home"></i>
@ -221,7 +221,7 @@
</span>
<span class="title">Download</span>
</a>
</li>
</li> -->
</ul>
</div>

View File

@ -1,4 +1,6 @@
<!DOCTYPE html>
{% load static %}
<html>
<head>
<meta charset="utf-8">
@ -7,14 +9,16 @@
Django Adminator - {% block title %}{% endblock %} | AppSeed
</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.21/css/jquery.dataTables.min.css"
integrity="sha512-1k7mWiTNoyx2XtmI96o+hdjP8nn0f3Z2N4oF/9ZZRgijyV4omsKOXEnqL1gKQNPy2MTSP9rIEWGcH/CInulptA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<link type="text/css" href="{{ ASSETS_ROOT }}/css/index.css" rel="stylesheet">
<link type="text/css" rel="stylesheet" href="{% static '/css/bootstrap.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/colours.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/datepicker.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/fontawesome.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/themify-icons.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/scrollbar.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/adminator.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/jquery.dataTables.min.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/select2.min.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/index.css' %}" />
<!-- Specific Page CSS goes HERE -->
{% block stylesheets %}{% endblock stylesheets %}
@ -22,7 +26,7 @@
</head>
<body class="app">
<div id='loader'>
<div id='loader' class="bg-body">
<div class="spinner"></div>
</div>

View File

@ -1,4 +1,6 @@
<!DOCTYPE html>
{% load static %}
<html>
<head>
<meta charset="utf-8">
@ -7,14 +9,16 @@
Django Adminator - {% block title %}{% endblock %} | AppSeed
</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/datatables/1.10.21/css/jquery.dataTables.min.css"
integrity="sha512-1k7mWiTNoyx2XtmI96o+hdjP8nn0f3Z2N4oF/9ZZRgijyV4omsKOXEnqL1gKQNPy2MTSP9rIEWGcH/CInulptA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<link type="text/css" href="{{ ASSETS_ROOT }}/css/index.css" rel="stylesheet">
<link type="text/css" rel="stylesheet" href="{% static '/css/bootstrap.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/colours.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/datepicker.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/fontawesome.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/themify-icons.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/scrollbar.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/adminator.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/jquery.dataTables.min.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/select2.min.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/index.css' %}" />
<!-- Specific Page CSS goes HERE -->
{% block stylesheets %}{% endblock stylesheets %}

View File

@ -1,4 +1,6 @@
<!DOCTYPE html>
{% load static %}
<html>
<head>
<meta charset="utf-8">
@ -7,15 +9,22 @@
Django Adminator - {% block title %}{% endblock %}
</title>
<link type="text/css" rel="stylesheet" href="{{ ASSETS_ROOT }}/css/jquery.dataTables.min.css" />
<link type="text/css" rel="stylesheet" href="{{ ASSETS_ROOT }}/css/index.css" />
<link type="text/css" rel="stylesheet" href="{{ ASSETS_ROOT }}/css/select2.min.css" />
<link type="text/css" rel="stylesheet" href="{% static '/css/bootstrap.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/colours.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/datepicker.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/fontawesome.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/themify-icons.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/perfectscrollbar.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/adminator.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/jquery.dataTables.min.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/select2.min.css' %}" />
<link type="text/css" rel="stylesheet" href="{% static '/css/index.css' %}" />
<!-- Specific Page CSS goes HERE -->
{% block stylesheets %}{% endblock stylesheets %}
</head>
<body class="app">
<body class="app" data-bs-theme="light">
<!-- @TOC -->
<!-- =================================================== -->
@ -33,7 +42,7 @@
<!-- @Page Loader -->
<!-- =================================================== -->
<div id='loader'>
<div id='loader' class="bg-body">
<div class="spinner"></div>
</div>

View File

@ -1,31 +1,26 @@
# -*- encoding: utf-8 -*-
import os, environ
from pathlib import Path
env = environ.Env(
# set casting, default value
DEBUG=(bool, True)
)
from django.utils import timezone
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(__file__))
CORE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# BASE_DIR is the root of the project, all paths should be constructed from it using pathlib
BASE_DIR = Path(__file__).parent.parent
# Take environment variables from .env file
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
# Create an environment and read variables from .env file
env = environ.Env(DEBUG=(bool, True))
environ.Env.read_env(BASE_DIR / ".env")
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = env('SECRET_KEY', default='S#perS3crEt_007')
# SECURITY WARNING: This is sensitive data, keep secure!
SECRET_KEY = env('SECRET_KEY', default="unsecure-default-secret-key")
# SECURITY WARNING: don't run with debug turned on in production!
# SECURITY WARNING: Must be 'False' in production!
DEBUG = env('DEBUG')
# Assets Management
ASSETS_ROOT = os.getenv('ASSETS_ROOT', '/static/assets')
# load production server from .env
ALLOWED_HOSTS = ['localhost', 'localhost:85', '127.0.0.1', '192.168.0.19', env('SERVER', default='127.0.0.1') ]
CSRF_TRUSTED_ORIGINS = ['http://localhost:85', 'http://127.0.0.1', 'https://' + env('SERVER', default='127.0.0.1') ]
# Hosts and Origins that the server host must be within.
ALLOWED_HOSTS = ["localhost", "127.0.0.1", env("HOST", default="127.0.0.1")]
CSRF_TRUSTED_ORIGINS = ["http://localhost", "http://127.0.0.1", "https://" + env("HOST", default="127.0.0.1")]
# Application definition
@ -36,7 +31,11 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'apps.home', # Enable the inner home (home)
'rest_framework',
"rest_framework.authtoken",
"django_filters",
'apps.api',
'apps.home',
'apps.authentication'
]
@ -52,14 +51,18 @@ MIDDLEWARE = [
]
ROOT_URLCONF = 'core.urls'
LOGIN_REDIRECT_URL = "home" # Route defined in home/urls.py
LOGOUT_REDIRECT_URL = "home" # Route defined in home/urls.py
TEMPLATE_DIR = os.path.join(CORE_DIR, "apps/templates") # ROOT dir for templates
APPEND_SLASH = True
LOGIN_URL = "/login/"
LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/"
AUTH_USER_MODEL = "authentication.User"
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [TEMPLATE_DIR],
'DIRS': [BASE_DIR / "apps/templates"],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@ -67,7 +70,6 @@ TEMPLATES = [
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'apps.context_processors.cfg_assets_root',
],
},
},
@ -75,8 +77,9 @@ TEMPLATES = [
WSGI_APPLICATION = 'core.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
if os.environ.get('DB_ENGINE') and os.environ.get('DB_ENGINE') == "mysql":
DATABASES = {
@ -98,7 +101,7 @@ else:
}
# Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
@ -115,8 +118,66 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
# Logging
# https://docs.djangoproject.com/en/5.0/topics/logging/
LOGGING_DIR = BASE_DIR / "logs"
LOGGING_DIR.mkdir(exist_ok=True)
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': LOGGING_DIR / f'{timezone.now()}.log',
"formatter": "verbose",
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
"formatter": "verbose"
},
"timed_file": {
"level": "DEBUG",
"class": "logging.handlers.TimedRotatingFileHandler",
"when": "D",
"interval": 1,
"backupCount": 3,
"encoding": "UTF-8",
"filename": LOGGING_DIR / "debug.log",
"formatter": "verbose"
}
},
'loggers': {
"apps": {
"handlers": ["timed_file", "console"],
"level": "DEBUG",
"propagate": True
},
"django": {
"handlers": ["timed_file", "console"],
"level": "INFO",
"propagate": True
},
"django.request": {
"handlers": ["timed_file", "console"],
"level": "ERROR",
"propagate": True
}
},
"formatters": {
"verbose": {
"format": "[%(asctime)s] [%(levelname)s] [%(name)s]: %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S",
}
}
}
# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = 'en-gb'
@ -124,33 +185,34 @@ TIME_ZONE = 'Europe/London'
USE_I18N = True
USE_L10N = True
USE_TZ = True
#############################################################
# SRC: https://devcenter.heroku.com/articles/django-assets
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATIC_ROOT = os.path.join(CORE_DIR, 'staticfiles')
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATIC_URL = '/static/'
# Extra places for collectstatic to find static files.
STATICFILES_DIRS = (
os.path.join(CORE_DIR, 'apps/static'),
BASE_DIR / 'apps/static',
)
# Media Files
MEDIA_ROOT = os.path.join(CORE_DIR, 'media')
MEDIA_ROOT = BASE_DIR / 'media'
MEDIA_URL = '/media/'
#############################################################
#############################################################
# If route isnt found, try again with appended slash
APPEND_SLASH = True
# Django Rest Framework
# https://www.django-rest-framework.org/
AUTH_USER_MODEL = "authentication.User"
LOGIN_URL = "/login/"
REST_FRAMEWORK = {
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.AnonRateThrottle',
'rest_framework.throttling.UserRateThrottle'
],
'DEFAULT_THROTTLE_RATES': {
'anon': '100/day',
'user': '1000/day'
}
}

View File

@ -6,7 +6,8 @@ from django.conf.urls.static import static
from django.urls import path, include # add this
urlpatterns = [
path('admin/', admin.site.urls), # Django admin route
path('admin/', admin.site.urls),
path("api/", include(("apps.api.urls", "apps.api"), namespace="api")),
# ADD NEW Routes HERE
*static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),

BIN
examples/dark.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

BIN
examples/light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

11671
logs/debug.log.2024-01-18 Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,4 @@
#!/usr/bin/env python
"""
Copyright (c) 2019 - present AppSeed.us
"""
import os
import sys

View File

@ -4,6 +4,9 @@ bleach==6.1.0
dj-database-url==0.5.0
Django==3.2.16
django-environ==0.8.1
django-filter==23.5
django-rest-framework==0.1.0
djangorestframework==3.14.0
gunicorn==20.1.0
pillow==10.2.0
pycodestyle==2.8.0

21
scripts/fixtures.bat Normal file
View File

@ -0,0 +1,21 @@
@echo off
echo migrating
python manage.py migrate
echo installing authentication fixtures
python manage.py loaddata apps\authentication\fixtures\department.json
echo installing default users
python manage.py loaddata apps\authentication\fixtures\user.json
echo installing ticket priorities
python manage.py loaddata apps\home\fixtures\ticketpriority.json
echo installing ticket tags
python manage.py loaddata apps\home\fixtures\tickettag.json
echo installing default tickets
python manage.py loaddata apps\home\fixtures\ticket.json
echo all done!

19
scripts/fixtures.ps1 Normal file
View File

@ -0,0 +1,19 @@
Write-Host "migrating"
python manage.py migrate
Write-Host "installing authentication fixtures"
python manage.py loaddata apps\authentication\fixtures\department.json
Write-Host "installing default users"
python manage.py loaddata apps\authentication\fixtures\user.json
Write-Host "installing ticket priorities"
python manage.py loaddata apps\home\fixtures\ticketpriority.json
Write-Host "installing ticket tags"
python manage.py loaddata apps\home\fixtures\tickettag.json
Write-Host "installing default tickets"
python manage.py loaddata apps\home\fixtures\ticket.json
Write-Host "all done!"

19
scripts/fixtures.sh Executable file
View File

@ -0,0 +1,19 @@
echo migrating
python manage.py migrate
echo installing authentication fixtures
python manage.py loaddata apps/authentication/fixtures/department.json
echo installing default users
python manage.py loaddata apps/authentication/fixtures/user.json
echo installing ticket priorities
python manage.py loaddata apps/home/fixtures/ticketpriority.json
echo installing ticket tags
python manage.py loaddata apps/home/fixtures/tickettag.json
echo installing default tickets
python manage.py loaddata apps/home/fixtures/ticket.json
echo all done!

Some files were not shown because too many files have changed in this diff Show More