default migrations
This commit is contained in:
parent
f630e00c21
commit
a59baefd8f
@ -1,8 +1,8 @@
|
|||||||
# -*- encoding: utf-8 -*-
|
# -*- encoding: utf-8 -*-
|
||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
|
||||||
|
|
||||||
from .models import User
|
from .models import User, Department
|
||||||
|
|
||||||
admin.site.register(User, UserAdmin)
|
admin.site.register(User)
|
||||||
|
admin.site.register(Department)
|
||||||
|
@ -5,14 +5,15 @@ Copyright (c) 2019 - present AppSeed.us
|
|||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.forms import UserCreationForm
|
from django.contrib.auth.forms import UserCreationForm
|
||||||
from django.contrib.auth.models import User
|
|
||||||
|
from .models import User
|
||||||
|
|
||||||
|
|
||||||
class LoginForm(forms.Form):
|
class LoginForm(forms.Form):
|
||||||
username = forms.CharField(
|
email = forms.EmailField(
|
||||||
widget=forms.TextInput(
|
widget=forms.EmailInput(
|
||||||
attrs={
|
attrs={
|
||||||
"placeholder": "Username",
|
"placeholder": "Email Address",
|
||||||
"class": "form-control"
|
"class": "form-control"
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
@ -26,10 +27,17 @@ class LoginForm(forms.Form):
|
|||||||
|
|
||||||
|
|
||||||
class SignUpForm(UserCreationForm):
|
class SignUpForm(UserCreationForm):
|
||||||
username = forms.CharField(
|
forename = forms.CharField(
|
||||||
widget=forms.TextInput(
|
widget=forms.TextInput(
|
||||||
attrs={
|
attrs={
|
||||||
"placeholder": "Username",
|
"placeholder": "Forename",
|
||||||
|
"class": "form-control"
|
||||||
|
}
|
||||||
|
))
|
||||||
|
surname = forms.CharField(
|
||||||
|
widget=forms.TextInput(
|
||||||
|
attrs={
|
||||||
|
"placeholder": "Surname",
|
||||||
"class": "form-control"
|
"class": "form-control"
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
@ -50,11 +58,11 @@ class SignUpForm(UserCreationForm):
|
|||||||
password2 = forms.CharField(
|
password2 = forms.CharField(
|
||||||
widget=forms.PasswordInput(
|
widget=forms.PasswordInput(
|
||||||
attrs={
|
attrs={
|
||||||
"placeholder": "Password check",
|
"placeholder": "Password (Again)",
|
||||||
"class": "form-control"
|
"class": "form-control"
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ('username', 'email', 'password1', 'password2')
|
fields = ('forename', 'surname', 'email', 'password1', 'password2')
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
# Generated by Django 3.2.16 on 2024-01-04 14:05
|
# Generated by Django 3.2.16 on 2024-01-05 12:04
|
||||||
|
|
||||||
import django.contrib.auth.models
|
|
||||||
import django.contrib.auth.validators
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
@ -16,30 +15,36 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Department',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('title', models.CharField(max_length=150)),
|
||||||
|
('icon', models.CharField(blank=True, max_length=32, null=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='User',
|
name='User',
|
||||||
fields=[
|
fields=[
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
||||||
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
|
||||||
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
|
|
||||||
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
|
|
||||||
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
|
|
||||||
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
|
|
||||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
|
||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
|
||||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
|
||||||
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('icon', models.ImageField(default='users/default.webp', upload_to='users', 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')),
|
||||||
|
('create_timestamp', models.DateTimeField(default=django.utils.timezone.now, help_text='When the user was created.', verbose_name='Creation Date')),
|
||||||
|
('edit_timestamp', models.DateTimeField(default=django.utils.timezone.now, help_text='When the user was last edited.', verbose_name='Last Edited')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Use as a "soft delete" rather than deleting the user.', verbose_name='active status')),
|
||||||
|
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates whether the user has unrestricted site control.', verbose_name='superuser status')),
|
||||||
|
('department', models.ForeignKey(blank=True, help_text='Which department does this user belong to?', null=True, on_delete=django.db.models.deletion.SET_NULL, to='authentication.department', verbose_name='department')),
|
||||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
|
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')),
|
||||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
|
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'user',
|
'verbose_name': 'user',
|
||||||
'verbose_name_plural': 'users',
|
'verbose_name_plural': 'users',
|
||||||
'abstract': False,
|
|
||||||
},
|
},
|
||||||
managers=[
|
|
||||||
('objects', django.contrib.auth.models.UserManager()),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 3.2.16 on 2024-01-04 14:17
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('authentication', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='icon',
|
|
||||||
field=models.ImageField(default='users/default.webp', upload_to='users'),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,24 +0,0 @@
|
|||||||
# Generated by Django 3.2.16 on 2024-01-04 14:47
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('authentication', '0002_user_icon'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='user',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(default='Corban-Lee Jones', max_length=150),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='user',
|
|
||||||
name='email',
|
|
||||||
field=models.EmailField(max_length=254, unique=True),
|
|
||||||
),
|
|
||||||
]
|
|
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 3.2.16 on 2024-01-04 22:15
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('authentication', '0003_auto_20240104_1447'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelManagers(
|
|
||||||
name='user',
|
|
||||||
managers=[
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
35
apps/authentication/migrations/default_departments.py
Normal file
35
apps/authentication/migrations/default_departments.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 3.2.16 on 2024-01-05 10:44
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.apps import apps
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
def create_departments(app, schema_editor):
|
||||||
|
|
||||||
|
Department = apps.get_model("authentication", "Department")
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{"title": "Development", "icon": None},
|
||||||
|
{"title": "Sales", "icon": None},
|
||||||
|
{"title": "Marketing", "icon": None},
|
||||||
|
{"title": "Management", "icon": None},
|
||||||
|
{"title": "Business Strategy", "icon": None},
|
||||||
|
]
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
priority = Department.objects.create(title=item["title"], icon=item["icon"])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentication", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(create_departments)
|
||||||
|
]
|
@ -3,23 +3,39 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.contrib.auth.models import AbstractUser, BaseUserManager
|
from django.utils import timezone
|
||||||
|
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
class Department(models.Model):
|
||||||
|
title = models.CharField(max_length=150)
|
||||||
|
icon = models.CharField(max_length=32, null=True, blank=True)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
def serialize(self) -> dict:
|
||||||
|
return {
|
||||||
|
"title": self.title,
|
||||||
|
"icon": self.icon
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class UserManager(BaseUserManager):
|
class UserManager(BaseUserManager):
|
||||||
|
|
||||||
def create_user(self, email: str, name: str, password: str=None, **extra_fields):
|
def create_user(self, email: str, forename: str, surname: str, password: str=None, **extra_fields):
|
||||||
if not email:
|
if not email:
|
||||||
raise ValueError("Please provide an email address")
|
raise ValueError("Please provide an email address")
|
||||||
|
|
||||||
email = self.normalize_email(email)
|
email = self.normalize_email(email)
|
||||||
user = self.model(email=email, name=name, **extra_fields)
|
user = self.model(email=email, forename=forename, surname=surname, **extra_fields)
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save()
|
user.save()
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def create_superuser(self, email: str, name: str, password: str, **extra_fields):
|
def create_superuser(self, email: str, forename: str, surname: str, password: str, **extra_fields):
|
||||||
extra_fields.setdefault("is_staff", True)
|
extra_fields.setdefault("is_staff", True)
|
||||||
extra_fields.setdefault("is_superuser", True)
|
extra_fields.setdefault("is_superuser", True)
|
||||||
extra_fields.setdefault("is_active", True)
|
extra_fields.setdefault("is_active", True)
|
||||||
@ -30,27 +46,108 @@ class UserManager(BaseUserManager):
|
|||||||
if not extra_fields.get("is_superuser"):
|
if not extra_fields.get("is_superuser"):
|
||||||
raise ValueError("Superuser must have is_superuser=True")
|
raise ValueError("Superuser must have is_superuser=True")
|
||||||
|
|
||||||
return self.create_user(email, name, password, **extra_fields)
|
return self.create_user(email, forename, surname, password, **extra_fields)
|
||||||
|
|
||||||
|
|
||||||
class User(AbstractUser):
|
class User(AbstractBaseUser, PermissionsMixin):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
icon = models.ImageField(upload_to="users", default="users/default.webp")
|
|
||||||
name = models.CharField(max_length=150)
|
icon = models.ImageField(_("profile picture"), upload_to="users", default="users/default.webp")
|
||||||
email = models.EmailField(unique=True)
|
email = models.EmailField(
|
||||||
|
_("email address"),
|
||||||
|
unique=True,
|
||||||
|
error_messages = {
|
||||||
|
"unique": _("A user with this email address already exists.")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
forename = models.CharField(
|
||||||
|
_("first name"),
|
||||||
|
max_length=150,
|
||||||
|
help_text=_("This should be your real first name.")
|
||||||
|
)
|
||||||
|
surname = models.CharField(
|
||||||
|
_("last name"),
|
||||||
|
max_length=150,
|
||||||
|
help_text=_("This should be your real last name.")
|
||||||
|
)
|
||||||
|
|
||||||
|
department = models.ForeignKey(
|
||||||
|
Department,
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
verbose_name=_("department"),
|
||||||
|
help_text=_("Which department does this user belong to?")
|
||||||
|
)
|
||||||
|
|
||||||
|
create_timestamp = models.DateTimeField(
|
||||||
|
_("Creation Date"),
|
||||||
|
editable=True,
|
||||||
|
default=timezone.now,
|
||||||
|
help_text=_("When the user was created.")
|
||||||
|
)
|
||||||
|
edit_timestamp = models.DateTimeField(
|
||||||
|
_("Last Edited"),
|
||||||
|
editable=True,
|
||||||
|
default=timezone.now,
|
||||||
|
help_text=_("When the user was last edited.")
|
||||||
|
)
|
||||||
|
|
||||||
|
is_active = models.BooleanField(
|
||||||
|
_("active status"),
|
||||||
|
default=True,
|
||||||
|
help_text=_('Use as a "soft delete" rather than deleting the user.')
|
||||||
|
)
|
||||||
|
is_staff = models.BooleanField(
|
||||||
|
_("staff status"),
|
||||||
|
default=False,
|
||||||
|
help_text=_("Designates whether the user can log into this admin site.")
|
||||||
|
)
|
||||||
|
is_superuser = models.BooleanField(
|
||||||
|
_("superuser status"),
|
||||||
|
default=False,
|
||||||
|
help_text=_("Designates whether the user has unrestricted site control.")
|
||||||
|
)
|
||||||
|
|
||||||
USERNAME_FIELD = "email"
|
USERNAME_FIELD = "email"
|
||||||
REQUIRED_FIELDS = ["name"]
|
EMAIL_FIELD = "email"
|
||||||
|
REQUIRED_FIELDS = ["forename", "surname"]
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('user')
|
||||||
|
verbose_name_plural = _('users')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return f"{self.id} • {self.email} • {self.formal_fullname}"
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
self.edit_timestamp = timezone.now()
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
super().clean()
|
||||||
|
self.email = self.__class__.objects.normalize_email(self.email)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fullname(self) -> str:
|
||||||
|
return f"{self.forename} {self.surname}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def formal_fullname(self) -> str:
|
||||||
|
return f"{self.surname}, {self.forename}"
|
||||||
|
|
||||||
def serialize(self) -> dict:
|
def serialize(self) -> dict:
|
||||||
|
department = self.department.serialize() if self.department else None
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"id": self.id,
|
"id": self.id,
|
||||||
"icon": self.icon.url,
|
"icon": self.icon.url,
|
||||||
"name": self.name,
|
"email": self.email,
|
||||||
"email": self.email
|
"forename": self.forename,
|
||||||
|
"surname": self.surname,
|
||||||
|
"department": department,
|
||||||
|
"create_timestamp": self.create_timestamp,
|
||||||
|
"edit_timestamp": self.edit_timestamp
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@ def login_view(request):
|
|||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
username = form.cleaned_data.get("username")
|
email = form.cleaned_data.get("email")
|
||||||
password = form.cleaned_data.get("password")
|
password = form.cleaned_data.get("password")
|
||||||
user = authenticate(username=username, password=password)
|
user = authenticate(username=email, password=password)
|
||||||
if user is not None:
|
if user is not None:
|
||||||
login(request, user)
|
login(request, user)
|
||||||
return redirect("/")
|
return redirect("/")
|
||||||
@ -36,10 +36,16 @@ def register_user(request):
|
|||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
form = SignUpForm(request.POST)
|
form = SignUpForm(request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
user = form.save(commit=False)
|
||||||
username = form.cleaned_data.get("username")
|
|
||||||
|
# Develepment, give all new users admin
|
||||||
|
user.is_staff = True
|
||||||
|
user.is_superuser = True
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
email = form.cleaned_data.get("email")
|
||||||
raw_password = form.cleaned_data.get("password1")
|
raw_password = form.cleaned_data.get("password1")
|
||||||
user = authenticate(username=username, password=raw_password)
|
user = authenticate(username=email, password=raw_password)
|
||||||
|
|
||||||
msg = 'User created successfully.'
|
msg = 'User created successfully.'
|
||||||
success = True
|
success = True
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Ticket
|
from .models import Ticket, TicketPriority, TicketTag
|
||||||
|
|
||||||
admin.site.register(Ticket)
|
admin.site.register(Ticket)
|
||||||
|
admin.site.register(TicketPriority)
|
||||||
|
admin.site.register(TicketTag)
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
# Generated by Django 3.2.16 on 2024-01-04 14:05
|
# Generated by Django 3.2.16 on 2024-01-05 10:44
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
@ -34,12 +35,14 @@ class Migration(migrations.Migration):
|
|||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Ticket',
|
name='Ticket',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
('title', models.CharField(max_length=60)),
|
('title', models.CharField(help_text='An extremely short summary of the ticket subject.', max_length=100, verbose_name='title')),
|
||||||
('description', models.TextField(max_length=650)),
|
('description', models.TextField(help_text='Detailed description of the ticket subject.', max_length=650, verbose_name='description')),
|
||||||
('create_timestamp', models.DateTimeField(default=django.utils.timezone.now)),
|
('create_timestamp', models.DateTimeField(default=django.utils.timezone.now, help_text='When the user was created.', verbose_name='Creation Date')),
|
||||||
('edit_timestamp', models.DateTimeField(default=django.utils.timezone.now)),
|
('edit_timestamp', models.DateTimeField(default=django.utils.timezone.now, help_text='When the user was last edited.', verbose_name='Last Edited')),
|
||||||
('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
('author', models.ForeignKey(help_text='The creator of the ticket.', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='author')),
|
||||||
|
('priority', models.ForeignKey(help_text='The importance level of this ticket.', on_delete=django.db.models.deletion.CASCADE, to='home.ticketpriority', verbose_name='priority')),
|
||||||
|
('tags', models.ManyToManyField(blank=True, help_text='Categories of the ticket.', to='home.TicketTag', verbose_name='tags')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
# Generated by Django 3.2.16 on 2024-01-04 23:51
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('home', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='ticket',
|
|
||||||
name='id',
|
|
||||||
field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False),
|
|
||||||
),
|
|
||||||
]
|
|
34
apps/home/migrations/default_priorities.py
Normal file
34
apps/home/migrations/default_priorities.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Generated by Django 3.2.16 on 2024-01-05 10:44
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.apps import apps
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
def create_priorities(app, schema_editor):
|
||||||
|
|
||||||
|
TicketPriority = apps.get_model("home", "TicketPriority")
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{"title": "Urgent", "colour": "#FF0000"},
|
||||||
|
{"title": "High", "colour": "#FFA500"},
|
||||||
|
{"title": "Normal", "colour": "#FFFF00"},
|
||||||
|
{"title": "Low", "colour": "#008000"}
|
||||||
|
]
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
priority = TicketPriority.objects.create(title=item["title"], colour=item["colour"])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("home", "0001_initial"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(create_priorities)
|
||||||
|
]
|
36
apps/home/migrations/default_tags.py
Normal file
36
apps/home/migrations/default_tags.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 3.2.16 on 2024-01-05 10:44
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.apps import apps
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
def create_tags(app, schema_editor):
|
||||||
|
|
||||||
|
TicketTag = apps.get_model("home", "TicketTag")
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{"title": "Network", "colour": "#0000FF"},
|
||||||
|
{"title": "Software", "colour": "#FFA500"},
|
||||||
|
{"title": "Hardware", "colour": "#808080"},
|
||||||
|
{"title": "Question", "colour": "#FFFF00"},
|
||||||
|
{"title": "Require's Help", "colour": "#00FF00"},
|
||||||
|
{"title": "Issue", "colour": "#FF0000"}
|
||||||
|
]
|
||||||
|
|
||||||
|
for item in data:
|
||||||
|
priority = TicketTag.objects.create(title=item["title"], colour=item["colour"])
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("home", "default_priorities"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(create_tags)
|
||||||
|
]
|
@ -7,28 +7,86 @@ from datetime import timedelta, datetime
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class TicketPriority(models.Model):
|
class TicketPriority(models.Model):
|
||||||
title = models.CharField(max_length=32)
|
title = models.CharField(max_length=32)
|
||||||
colour = models.CharField(max_length=7)
|
colour = models.CharField(max_length=7)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
def serialize(self) -> dict:
|
||||||
|
return {
|
||||||
|
"title": self.title,
|
||||||
|
"colour": self.colour
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TicketTag(models.Model):
|
class TicketTag(models.Model):
|
||||||
title = models.CharField(max_length=32)
|
title = models.CharField(max_length=32)
|
||||||
colour = models.CharField(max_length=7)
|
colour = models.CharField(max_length=7)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
def serialize(self) -> dict:
|
||||||
|
return {
|
||||||
|
"title": self.title,
|
||||||
|
"colour": self.colour
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Ticket(models.Model):
|
class Ticket(models.Model):
|
||||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
title = models.CharField(max_length=60)
|
|
||||||
description = models.TextField(max_length=650)
|
title = models.CharField(
|
||||||
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
|
_("title"),
|
||||||
create_timestamp = models.DateTimeField(editable=True, default=timezone.now)
|
max_length=100,
|
||||||
edit_timestamp = models.DateTimeField(editable=True, default=timezone.now)
|
help_text=_("An extremely short summary of the ticket subject.")
|
||||||
|
)
|
||||||
|
description = models.TextField(
|
||||||
|
_("description"),
|
||||||
|
max_length=650,
|
||||||
|
help_text=_("Detailed description of the ticket subject.")
|
||||||
|
)
|
||||||
|
author = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
verbose_name=_("author"),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
help_text=_("The creator of the ticket.")
|
||||||
|
)
|
||||||
|
|
||||||
|
priority = models.ForeignKey(
|
||||||
|
TicketPriority,
|
||||||
|
verbose_name=_("priority"),
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
help_text=_("The importance level of this ticket.")
|
||||||
|
)
|
||||||
|
tags = models.ManyToManyField(
|
||||||
|
TicketTag,
|
||||||
|
verbose_name=_("tags"),
|
||||||
|
blank=True,
|
||||||
|
help_text=_("Categories of the ticket.")
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
create_timestamp = models.DateTimeField(
|
||||||
|
_("Creation Date"),
|
||||||
|
editable=True,
|
||||||
|
default=timezone.now,
|
||||||
|
help_text=_("When the user was created.")
|
||||||
|
)
|
||||||
|
edit_timestamp = models.DateTimeField(
|
||||||
|
_("Last Edited"),
|
||||||
|
editable=True,
|
||||||
|
default=timezone.now,
|
||||||
|
help_text=_("When the user was last edited.")
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"#{self.id} • {self.title} • {self.author}"
|
return f"#{self.id} • {self.title} •{f' {self.author.department.title} •' if self.author.department else ''} {self.author.formal_fullname}"
|
||||||
|
|
||||||
def clean_description(self):
|
def clean_description(self):
|
||||||
cleaned_description = bleach.clean(
|
cleaned_description = bleach.clean(
|
||||||
@ -40,6 +98,7 @@ class Ticket(models.Model):
|
|||||||
|
|
||||||
def save(self, *args, **kwargs):
|
def save(self, *args, **kwargs):
|
||||||
self.description = self.clean_description()
|
self.description = self.clean_description()
|
||||||
|
self.edit_timestamp = timezone.now()
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -91,5 +150,7 @@ class Ticket(models.Model):
|
|||||||
"edit_timestamp": self.edit_timestamp,
|
"edit_timestamp": self.edit_timestamp,
|
||||||
"is_edited": self.is_edited,
|
"is_edited": self.is_edited,
|
||||||
"is_older_than_day": self.is_older_than_day,
|
"is_older_than_day": self.is_older_than_day,
|
||||||
"timestamp": self.timestamp
|
"timestamp": self.timestamp,
|
||||||
|
"priority": self.priority.serialize(),
|
||||||
|
"tags": [tag.serialize() for tag in self.tags.all()]
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,8 @@ from django.shortcuts import render
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.forms.models import model_to_dict
|
from django.forms.models import model_to_dict
|
||||||
|
|
||||||
from .models import Ticket
|
from ..authentication.models import Department
|
||||||
|
from .models import Ticket, TicketPriority, TicketTag
|
||||||
|
|
||||||
|
|
||||||
@login_required()
|
@login_required()
|
||||||
@ -22,8 +23,15 @@ def dashboard(request):
|
|||||||
@login_required()
|
@login_required()
|
||||||
def tickets(request):
|
def tickets(request):
|
||||||
tickets = Ticket.objects.all().order_by("-create_timestamp")
|
tickets = Ticket.objects.all().order_by("-create_timestamp")
|
||||||
|
priorities = TicketPriority.objects.all()
|
||||||
|
tags = TicketTag.objects.all()
|
||||||
|
departments = Department.objects.all()
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
"tickets": tickets,
|
"tickets": tickets,
|
||||||
|
"priorities": priorities,
|
||||||
|
"tags": tags,
|
||||||
|
"departments": departments,
|
||||||
"dayago": datetime.now() - timedelta(hours=24)
|
"dayago": datetime.now() - timedelta(hours=24)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,10 +32,10 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="text-normal text-dark form-label">Username</label>
|
<label class="text-normal text-dark form-label">Email Address</label>
|
||||||
{{ form.username }}
|
{{ form.email }}
|
||||||
</div>
|
</div>
|
||||||
<span class="text-error">{{ form.username.errors }}</span>
|
<span class="text-error">{{ form.email.errors }}</span>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="text-normal text-dark form-label">Password</label>
|
<label class="text-normal text-dark form-label">Password</label>
|
||||||
|
@ -40,10 +40,16 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" class="text-normal text-dark">Username</label>
|
<label class="form-label" class="text-normal text-dark">Forename</label>
|
||||||
{{ form.username }}
|
{{ form.forename }}
|
||||||
</div>
|
</div>
|
||||||
<span class="text-error">{{ form.username.errors }}</span>
|
<span class="text-error">{{ form.forename.errors }}</span>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label" class="text-normal text-dark">Surname</label>
|
||||||
|
{{ form.surname }}
|
||||||
|
</div>
|
||||||
|
<span class="text-error">{{ form.surname.errors }}</span>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" class="text-normal text-dark">Email Address</label>
|
<label class="form-label" class="text-normal text-dark">Email Address</label>
|
||||||
@ -58,7 +64,7 @@
|
|||||||
<span class="text-error">{{ form.password1.errors }}</span>
|
<span class="text-error">{{ form.password1.errors }}</span>
|
||||||
|
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" class="text-normal text-dark">Password Check</label>
|
<label class="form-label" class="text-normal text-dark">Password (Again)</label>
|
||||||
{{ form.password2 }}
|
{{ form.password2 }}
|
||||||
</div>
|
</div>
|
||||||
<span class="text-error">{{ form.password2.errors }}</span>
|
<span class="text-error">{{ form.password2.errors }}</span>
|
||||||
|
@ -22,39 +22,43 @@
|
|||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<h6>Filters</h6>
|
<h6>Filters</h6>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item ps-3 mT-15">
|
|
||||||
<h6 class="small">Priority</h6>
|
{% if priorities %}
|
||||||
<div class="form-check">
|
<li class="nav-item ps-3 mT-15">
|
||||||
<input type="checkbox" id="priority1" class="form-check-input">
|
<h6 class="small">Priority</h6>
|
||||||
<label for="priority1" class="form-check-label">High</label>
|
{% for priority in priorities %}
|
||||||
</div>
|
<div class="form-check">
|
||||||
<div class="form-check">
|
<input type="checkbox" id="priority-{{ priority.id }}" class="form-check-input">
|
||||||
<input type="checkbox" id="priority2" class="form-check-input">
|
<label for="priority-{{ priority.id }}" class="form-check-label">{{ priority.title }}</label>
|
||||||
<label for="priority2" class="form-check-label">Medium</label>
|
</div>
|
||||||
</div>
|
{% endfor %}
|
||||||
<div class="form-check">
|
</li>
|
||||||
<input type="checkbox" id="priority3" class="form-check-input">
|
{% endif %}
|
||||||
<label for="priority3" class="form-check-label">Low</label>
|
|
||||||
</div>
|
{% if departments %}
|
||||||
</li>
|
<li class="nav-item ps-3 mT-15">
|
||||||
<li class="nav-item ps-3 mT-15">
|
<h6 class="small">Department</h6>
|
||||||
<h6 class="small">Department</h6>
|
{% for department in departments %}
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
<input type="checkbox" id="sales" class="form-check-input">
|
<input type="checkbox" id="department-{{ department.id }}" class="form-check-input">
|
||||||
<label for="sales" class="form-check-label">Sales</label>
|
<label for="department-{{ department.id }}" class="form-check-label">{{ department.title }}</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check">
|
{% endfor %}
|
||||||
<input type="checkbox" id="strategy" class="form-check-input">
|
</li>
|
||||||
<label for="strategy" class="form-check-label">Strategy</label>
|
{% endif %}
|
||||||
</div>
|
|
||||||
<div class="form-check">
|
{% if tags %}
|
||||||
<input type="checkbox" id="marketing" class="form-check-input">
|
<li class="nav-item ps-3 mT-15">
|
||||||
<label for="marketing" class="form-check-label">Marketing</label>
|
<h6 class="small">Tags</h6>
|
||||||
</div>
|
{% for tag in tags %}
|
||||||
</li>
|
<div class="form-check">
|
||||||
<li class="nav-item ps-3 mT-15">
|
<input type="checkbox" id="tag-{{ tag.id }}" class="form-check-input">
|
||||||
<h6 class="small">Tags</h6>
|
<label for="tag-{{ tag.id }}" class="form-check-label">{{ tag.title }}</label>
|
||||||
</li>
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- <li class="nav-item mt-5">
|
<!-- <li class="nav-item mt-5">
|
||||||
<a href="javascript:void(0)" class="nav-link c-grey-800 cH-blue-500 actived">
|
<a href="javascript:void(0)" class="nav-link c-grey-800 cH-blue-500 actived">
|
||||||
<div class="peers ai-c jc-sb">
|
<div class="peers ai-c jc-sb">
|
||||||
@ -209,7 +213,7 @@
|
|||||||
<div class="peer peer-greed ov-h">
|
<div class="peer peer-greed ov-h">
|
||||||
<div class="peers ai-c">
|
<div class="peers ai-c">
|
||||||
<div class="peer peer-greed">
|
<div class="peer peer-greed">
|
||||||
<h6 class="ticket-author mb-0">{{ ticket.author }}</h6>
|
<h6 class="ticket-author mb-0">{{ ticket.author.fullname }}</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="peer">
|
<div class="peer">
|
||||||
<small class="ticket-timestamp">
|
<small class="ticket-timestamp">
|
||||||
@ -394,7 +398,7 @@
|
|||||||
$("#ticketAuthorImg").prop("src", "");
|
$("#ticketAuthorImg").prop("src", "");
|
||||||
$("#ticketTimestamp").text("");
|
$("#ticketTimestamp").text("");
|
||||||
$("#btnGroupDrop2").hide();
|
$("#btnGroupDrop2").hide();
|
||||||
$("#ticketBadges").hide();
|
$("#ticketBadges").empty().hide();
|
||||||
|
|
||||||
if (displayedTicketID === ticketID) {
|
if (displayedTicketID === ticketID) {
|
||||||
displayedTicketID = -1;
|
displayedTicketID = -1;
|
||||||
@ -411,19 +415,31 @@
|
|||||||
csrfmiddlewaretoken: '{{ csrf_token }}'
|
csrfmiddlewaretoken: '{{ csrf_token }}'
|
||||||
},
|
},
|
||||||
success: function (data) {
|
success: function (data) {
|
||||||
alert(JSON.stringify(data, null, 4));
|
console.log(JSON.stringify(data, null, 4));
|
||||||
|
|
||||||
var ticket = data.ticket;
|
var ticket = data.ticket;
|
||||||
var author = ticket.author;
|
var author = ticket.author;
|
||||||
|
var department = author.department;
|
||||||
|
var priority = ticket.priority;
|
||||||
|
|
||||||
$("#ticketTitle").text(ticket.title);
|
$("#ticketTitle").text(ticket.title);
|
||||||
$("#ticketDesc").append($(ticket.description));
|
$("#ticketDesc").append($(`<div>${ticket.description}</div>`));
|
||||||
$("#ticketAuthor").text(author.name);
|
$("#ticketAuthor").text(`${author.forename} ${author.surname}`);
|
||||||
$("#ticketAuthorImg").show();
|
$("#ticketAuthorImg").show();
|
||||||
$("#ticketAuthorImg").prop("src", author.icon);
|
$("#ticketAuthorImg").prop("src", author.icon);
|
||||||
$("#btnGroupDrop2").show();
|
$("#btnGroupDrop2").show();
|
||||||
$("#ticketBadges").show();
|
$("#ticketBadges").show();
|
||||||
|
|
||||||
|
$("#ticketBadges").append($(`<div class="badge me-1" style="background-color: ${priority.colour};">${priority.title}</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="background-color: ${tag.colour};">${tag.title}</div>`));
|
||||||
|
});
|
||||||
|
|
||||||
// timestamp
|
// timestamp
|
||||||
var timestamp = new Date(ticket.timestamp);
|
var timestamp = new Date(ticket.timestamp);
|
||||||
var formattedTime;
|
var formattedTime;
|
||||||
|
@ -184,7 +184,7 @@
|
|||||||
<img class="w-2r bdrs-50p" src="{{ request.user.icon.url }}" alt="">
|
<img class="w-2r bdrs-50p" src="{{ request.user.icon.url }}" alt="">
|
||||||
</div>
|
</div>
|
||||||
<div class="peer">
|
<div class="peer">
|
||||||
<span class="fsz-sm c-grey-900">{{ request.user }}</span>
|
<span class="fsz-sm c-grey-900">{{ request.user.formal_fullname }}</span>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu fsz-sm">
|
<ul class="dropdown-menu fsz-sm">
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 76 KiB |
BIN
media/users/download.png
Normal file
BIN
media/users/download.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
BIN
media/users/download_kprVYJv.png
Normal file
BIN
media/users/download_kprVYJv.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
Loading…
x
Reference in New Issue
Block a user