diff --git a/app/core/forms/ticket_categories.py b/app/core/forms/ticket_categories.py new file mode 100644 index 00000000..613bcd11 --- /dev/null +++ b/app/core/forms/ticket_categories.py @@ -0,0 +1,120 @@ +from django import forms +from django.forms import ValidationError +from django.urls import reverse + +from app import settings + +from core.forms.common import CommonModelForm +from core.models.ticket.ticket_category import TicketCategory + + + +class TicketCategoryForm(CommonModelForm): + + + class Meta: + + fields = '__all__' + + model = TicketCategory + + prefix = 'ticket_category' + + def __init__(self, *args, **kwargs): + + super().__init__(*args, **kwargs) + + self.fields['parent'].queryset = self.fields['parent'].queryset.exclude( + id=self.instance.pk + ) + + + def clean(self): + + cleaned_data = super().clean() + + pk = self.instance.id + + parent = cleaned_data.get("parent") + + if pk: + + if parent == pk: + + raise ValidationError("Category can't have itself as its parent category") + + return cleaned_data + + + +class DetailForm(TicketCategoryForm): + + + tabs: dict = { + "details": { + "name": "Details", + "slug": "details", + "sections": [ + { + "layout": "double", + "left": [ + 'parent', + 'name', + 'runbook', + 'organization', + 'c_created', + 'c_modified' + ], + "right": [ + 'model_notes', + ] + }, + { + "layout": "double", + "name": "Ticket Types", + "left": [ + 'change', + 'problem' + 'request' + ], + "right": [ + 'incident', + 'project_task' + ] + }, + ] + }, + "notes": { + "name": "Notes", + "slug": "notes", + "sections": [] + } + } + + + def __init__(self, *args, **kwargs): + + super().__init__(*args, **kwargs) + + + self.fields['c_created'] = forms.DateTimeField( + label = 'Created', + input_formats=settings.DATETIME_FORMAT, + disabled = True, + initial = self.instance.created, + ) + + self.fields['c_modified'] = forms.DateTimeField( + label = 'Modified', + input_formats=settings.DATETIME_FORMAT, + disabled = True, + initial = self.instance.modified, + ) + + + self.tabs['details'].update({ + "edit_url": reverse('Settings:_ticket_category_change', kwargs={'pk': self.instance.pk}) + }) + + self.url_index_view = reverse('Settings:_ticket_categories') + diff --git a/app/core/migrations/0006_ticketcategory.py b/app/core/migrations/0006_ticketcategory.py new file mode 100644 index 00000000..3dfe26ac --- /dev/null +++ b/app/core/migrations/0006_ticketcategory.py @@ -0,0 +1,43 @@ +# Generated by Django 5.0.8 on 2024-09-13 01:10 + +import access.fields +import access.models +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('access', '0001_initial'), + ('assistance', '0001_initial'), + ('core', '0005_ticket_relatedtickets_ticketcomment'), + ] + + operations = [ + migrations.CreateModel( + name='TicketCategory', + fields=[ + ('is_global', models.BooleanField(default=False)), + ('model_notes', models.TextField(blank=True, default=None, null=True, verbose_name='Notes')), + ('id', models.AutoField(help_text='Category ID Number', primary_key=True, serialize=False, unique=True, verbose_name='Number')), + ('created', access.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False)), + ('modified', access.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False)), + ('name', models.CharField(help_text='Category Name', max_length=50, verbose_name='Name')), + ('change', models.BooleanField(default=True, help_text='Use category for change tickets', verbose_name='Change Tickets')), + ('incident', models.BooleanField(default=True, help_text='Use category for incident tickets', verbose_name='Incident Tickets')), + ('problem', models.BooleanField(default=True, help_text='Use category for problem tickets', verbose_name='Problem Tickets')), + ('project_task', models.BooleanField(default=True, help_text='Use category for Project tasks', verbose_name='Project Tasks')), + ('request', models.BooleanField(default=True, help_text='Use category for request tickets', verbose_name='Request Tickets')), + ('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])), + ('parent', models.ForeignKey(blank=True, help_text='The Parent Category', null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.ticketcategory', verbose_name='Parent Category')), + ('runbook', models.ForeignKey(blank=True, help_text='The runbook for this category', null=True, on_delete=django.db.models.deletion.SET_NULL, to='assistance.knowledgebase', verbose_name='Runbook')), + ], + options={ + 'verbose_name': 'Ticket Category', + 'verbose_name_plural': 'Ticket Categories', + 'ordering': ['name'], + }, + ), + ] diff --git a/app/core/models/ticket/ticket_category.py b/app/core/models/ticket/ticket_category.py new file mode 100644 index 00000000..73e128f4 --- /dev/null +++ b/app/core/models/ticket/ticket_category.py @@ -0,0 +1,111 @@ +from django.db import models + +from access.fields import AutoCreatedField, AutoLastModifiedField +from access.models import TenancyObject, Team + +from assistance.models.knowledge_base import KnowledgeBase + + + +class TicketCategoryCommonFields(TenancyObject): + + class Meta: + abstract = True + + id = models.AutoField( + blank=False, + help_text = 'Category ID Number', + primary_key=True, + unique=True, + verbose_name = 'Number', + ) + + created = AutoCreatedField() + + modified = AutoLastModifiedField() + + + +class TicketCategory(TicketCategoryCommonFields): + + + class Meta: + + ordering = [ + 'name' + ] + + verbose_name = "Ticket Category" + + verbose_name_plural = "Ticket Categories" + + + parent = models.ForeignKey( + 'self', + blank= True, + help_text = 'The Parent Category', + null = True, + on_delete = models.SET_NULL, + verbose_name = 'Parent Category', + ) + + name = models.CharField( + blank = False, + help_text = "Category Name", + max_length = 50, + verbose_name = 'Name', + ) + + runbook = models.ForeignKey( + KnowledgeBase, + blank= True, + help_text = 'The runbook for this category', + null = True, + on_delete = models.SET_NULL, + verbose_name = 'Runbook', + ) + + change = models.BooleanField( + blank = False, + default = True, + help_text = 'Use category for change tickets', + null = False, + verbose_name = 'Change Tickets', + ) + + incident = models.BooleanField( + blank = False, + default = True, + help_text = 'Use category for incident tickets', + null = False, + verbose_name = 'Incident Tickets', + ) + + problem = models.BooleanField( + blank = False, + default = True, + help_text = 'Use category for problem tickets', + null = False, + verbose_name = 'Problem Tickets', + ) + + project_task = models.BooleanField( + blank = False, + default = True, + help_text = 'Use category for Project tasks', + null = False, + verbose_name = 'Project Tasks', + ) + + request = models.BooleanField( + blank = False, + default = True, + help_text = 'Use category for request tickets', + null = False, + verbose_name = 'Request Tickets', + ) + + + def __str__(self): + + return self.name diff --git a/app/core/templates/core/index_ticket_categories.html.j2 b/app/core/templates/core/index_ticket_categories.html.j2 new file mode 100644 index 00000000..47099704 --- /dev/null +++ b/app/core/templates/core/index_ticket_categories.html.j2 @@ -0,0 +1,29 @@ +{% extends 'base.html.j2' %} + +{% block content %} + + + + + + + + + + + {% if items %} + {% for category in items %} + + + + + + + + {% endfor %} + {% else %} + + {% endif%} +
NameOrganizationcreatedmodified 
{{ category.name }}{{ category.organization }}{{ category.created }}{{ category.modified }} 
Nothing Found
+ +{% endblock %} diff --git a/app/core/templates/core/ticket_category.html.j2 b/app/core/templates/core/ticket_category.html.j2 new file mode 100644 index 00000000..d2fc524f --- /dev/null +++ b/app/core/templates/core/ticket_category.html.j2 @@ -0,0 +1,12 @@ +{% extends 'detail.html.j2' %} + + +{% block tabs %} + +
+ + {% include 'content/section.html.j2' with tab=form.tabs.details %} + +
+ +{% endblock %} diff --git a/app/core/views/ticket_categories.py b/app/core/views/ticket_categories.py new file mode 100644 index 00000000..f0a3ac5c --- /dev/null +++ b/app/core/views/ticket_categories.py @@ -0,0 +1,168 @@ +from django.urls import reverse + +from core.forms.comment import AddNoteForm +from core.forms.ticket_categories import DetailForm, TicketCategory, TicketCategoryForm + +from core.models.notes import Notes +from core.views.common import AddView, ChangeView, DeleteView, IndexView + + + +class Add(AddView): + + form_class = TicketCategoryForm + + model = TicketCategory + + permission_required = [ + 'core.add_ticketcategory', + ] + + + def get_initial(self): + + initial = super().get_initial() + + if 'pk' in self.kwargs: + + if self.kwargs['pk']: + + initial.update({'parent': self.kwargs['pk']}) + + self.model.parent.field.hidden = True + + return initial + + + def get_success_url(self, **kwargs): + + return reverse('Settings:_ticket_categories') + + + +class Change(ChangeView): + + form_class = TicketCategoryForm + + model = TicketCategory + + permission_required = [ + 'core.change_ticketcategory', + ] + + + def get_context_data(self, **kwargs): + + context = super().get_context_data(**kwargs) + + context['content_title'] = str(self.object) + + return context + + + def get_success_url(self, **kwargs): + + return reverse('Settings:_ticket_category_view', args=(self.kwargs['pk'],)) + + + +class Delete(DeleteView): + + model = TicketCategory + + permission_required = [ + 'itim.delete_ticketcategory', + ] + + + def get_context_data(self, **kwargs): + + context = super().get_context_data(**kwargs) + + context['content_title'] = 'Delete ' + str(self.object) + + return context + + + def get_success_url(self, **kwargs): + + return reverse('Settings:_ticket_categories') + + + +class Index(IndexView): + + context_object_name = "items" + + model = TicketCategory + + paginate_by = 10 + + permission_required = [ + 'core.view_ticketcategory' + ] + + template_name = 'core/index_ticket_categories.html.j2' + + + def get_context_data(self, **kwargs): + + context = super().get_context_data(**kwargs) + + context['model_docs_path'] = self.model._meta.app_label + '/' + self.model._meta.model_name + + return context + + + +class View(ChangeView): + + context_object_name = "ticket_categories" + + form_class = DetailForm + + model = TicketCategory + + permission_required = [ + 'core.view_ticketcategory', + ] + + template_name = 'core/ticket_category.html.j2' + + + def get_context_data(self, **kwargs): + + context = super().get_context_data(**kwargs) + + context['notes_form'] = AddNoteForm(prefix='note') + context['notes'] = Notes.objects.filter(service=self.kwargs['pk']) + + context['model_pk'] = self.kwargs['pk'] + context['model_name'] = self.model._meta.model_name + + context['model_delete_url'] = reverse('Settings:_ticket_category_delete', kwargs={'pk': self.kwargs['pk']}) + + + context['content_title'] = self.object.name + + return context + + + # def post(self, request, *args, **kwargs): + + # item = Cluster.objects.get(pk=self.kwargs['pk']) + + # notes = AddNoteForm(request.POST, prefix='note') + + # if notes.is_bound and notes.is_valid() and notes.instance.note != '': + + # notes.instance.service = item + + # notes.instance.organization = item.organization + + # notes.save() + + + def get_success_url(self, **kwargs): + + return reverse('Settings:_ticket_category_view', kwargs={'pk': self.kwargs['pk']}) diff --git a/app/settings/templates/settings/home.html.j2 b/app/settings/templates/settings/home.html.j2 index fda2e5af..b6ec69cf 100644 --- a/app/settings/templates/settings/home.html.j2 +++ b/app/settings/templates/settings/home.html.j2 @@ -50,6 +50,13 @@ div#content article h3 { +
+

Core

+ +
+

ITAM