@ -24,7 +24,7 @@ from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||
|
||||
from .views import home
|
||||
|
||||
from core.views import history, related_ticket
|
||||
from core.views import history, related_ticket, ticket_linked_item
|
||||
|
||||
from settings.views import user_settings
|
||||
|
||||
@ -53,6 +53,8 @@ urlpatterns = [
|
||||
|
||||
path('ticket/<str:ticket_type>/<int:ticket_id>/relate/add', related_ticket.Add.as_view(), name="_ticket_related_add"),
|
||||
|
||||
path('ticket/<str:ticket_type>/<int:ticket_id>/linked_item/add', ticket_linked_item.Add.as_view(), name="_ticket_linked_item_add"),
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
26
app/core/forms/ticket_linked_item.py
Normal file
26
app/core/forms/ticket_linked_item.py
Normal file
@ -0,0 +1,26 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
from django.forms import ValidationError
|
||||
|
||||
from app import settings
|
||||
|
||||
from core.forms.common import CommonModelForm
|
||||
|
||||
from core.models.ticket.ticket_linked_items import TicketLinkedItem
|
||||
|
||||
|
||||
class TicketLinkedItemForm(CommonModelForm):
|
||||
|
||||
prefix = 'ticket_linked_item'
|
||||
|
||||
class Meta:
|
||||
model = TicketLinkedItem
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.fields['organization'].widget = self.fields['organization'].hidden_widget()
|
||||
self.fields['ticket'].widget = self.fields['ticket'].hidden_widget()
|
31
app/core/migrations/0011_ticketlinkeditem.py
Normal file
31
app/core/migrations/0011_ticketlinkeditem.py
Normal file
@ -0,0 +1,31 @@
|
||||
# Generated by Django 5.0.8 on 2024-09-20 05:50
|
||||
|
||||
import access.models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('access', '0001_initial'),
|
||||
('core', '0010_alter_ticketcategory_options'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='TicketLinkedItem',
|
||||
fields=[
|
||||
('id', models.AutoField(help_text='ID Number', primary_key=True, serialize=False, unique=True, verbose_name='Number')),
|
||||
('item_type', models.IntegerField(choices=[(1, 'Cluster'), (2, 'Config Group'), (3, 'Device'), (4, 'Operating System'), (5, 'Service'), (6, 'Software')], help_text='Python Model location for linked item', verbose_name='Item Type')),
|
||||
('item', models.IntegerField(help_text='Item ID to link to ticket', verbose_name='Item ID')),
|
||||
('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])),
|
||||
('ticket', models.ForeignKey(help_text='Ticket the item will be linked to', on_delete=django.db.models.deletion.CASCADE, to='core.ticket', verbose_name='Ticket')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Ticket Linked Item',
|
||||
'verbose_name_plural': 'Ticket linked Items',
|
||||
'ordering': ['id'],
|
||||
},
|
||||
),
|
||||
]
|
@ -3,7 +3,6 @@ from django.db import models
|
||||
from django.db.models import Q, signals, Sum
|
||||
from django.forms import ValidationError
|
||||
|
||||
# from .ticket_enum import TicketEnum
|
||||
from .ticket_enum_values import TicketValues
|
||||
|
||||
from access.fields import AutoCreatedField, AutoLastModifiedField
|
||||
@ -672,6 +671,30 @@ class Ticket(
|
||||
return str(duration)
|
||||
|
||||
|
||||
@property
|
||||
def linked_items(self) -> list(dict()):
|
||||
"""Fetch items linked to ticket
|
||||
|
||||
Returns:
|
||||
List of dict (list): List of dictionary with fields: id, name, type and url.
|
||||
Empty List (list): No items were found
|
||||
"""
|
||||
|
||||
linked_items: list = []
|
||||
|
||||
from core.models.ticket.ticket_linked_items import TicketLinkedItem
|
||||
|
||||
items = TicketLinkedItem.objects.filter(
|
||||
ticket = self
|
||||
)
|
||||
|
||||
if len(items) > 0:
|
||||
|
||||
linked_items = items
|
||||
|
||||
return linked_items
|
||||
|
||||
|
||||
@property
|
||||
def related_tickets(self) -> list(dict()):
|
||||
|
||||
|
@ -4,6 +4,7 @@ from .ticket_enum_values import TicketValues
|
||||
|
||||
from access.models import TenancyObject
|
||||
|
||||
from core.middleware.get_request import get_request
|
||||
from core.models.ticket.ticket import Ticket
|
||||
|
||||
|
||||
@ -16,10 +17,18 @@ class TicketLinkedItem(TenancyObject):
|
||||
'id'
|
||||
]
|
||||
|
||||
class Modules(models.TextChoices):
|
||||
DEVICE = 'itam.models.device', 'Device'
|
||||
OPERATING_SYSTEM = 'itam.models.operating_system', 'Operating System'
|
||||
SOFTWARE = 'itam.models.software', 'Software'
|
||||
verbose_name = 'Ticket Linked Item'
|
||||
|
||||
verbose_name_plural = 'Ticket linked Items'
|
||||
|
||||
|
||||
class Modules(models.IntegerChoices):
|
||||
CLUSTER = 1, 'Cluster'
|
||||
CONFIG_GROUP = 2, 'Config Group'
|
||||
DEVICE = 3, 'Device'
|
||||
OPERATING_SYSTEM = 4, 'Operating System'
|
||||
SERVICE = 5, 'Service'
|
||||
SOFTWARE = 6, 'Software'
|
||||
|
||||
is_global = None
|
||||
|
||||
@ -42,7 +51,7 @@ class TicketLinkedItem(TenancyObject):
|
||||
verbose_name = 'Ticket',
|
||||
)
|
||||
|
||||
module = models.CharField(
|
||||
item_type = models.IntegerField(
|
||||
blank= False,
|
||||
choices = Modules,
|
||||
help_text = 'Python Model location for linked item',
|
||||
@ -56,3 +65,78 @@ class TicketLinkedItem(TenancyObject):
|
||||
null = False,
|
||||
verbose_name = 'Item ID',
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
item_type: str = None
|
||||
|
||||
if self.item_type == TicketLinkedItem.Modules.CLUSTER:
|
||||
|
||||
item_type = 'cluster'
|
||||
|
||||
elif self.item_type == TicketLinkedItem.Modules.CONFIG_GROUP:
|
||||
|
||||
item_type = 'config_group'
|
||||
|
||||
elif self.item_type == TicketLinkedItem.Modules.DEVICE:
|
||||
|
||||
item_type = 'device'
|
||||
|
||||
elif self.item_type == TicketLinkedItem.Modules.OPERATING_SYSTEM:
|
||||
|
||||
item_type = 'operating_system'
|
||||
|
||||
elif self.item_type == TicketLinkedItem.Modules.SERVICE:
|
||||
|
||||
item_type = 'service'
|
||||
|
||||
elif self.item_type == TicketLinkedItem.Modules.SOFTWARE:
|
||||
|
||||
item_type = 'software'
|
||||
|
||||
if item_type:
|
||||
|
||||
return f'${item_type}-{int(self.item)}'
|
||||
|
||||
return str(self.item)
|
||||
|
||||
|
||||
@property
|
||||
def parent_object(self):
|
||||
""" Fetch the parent object """
|
||||
|
||||
return self.ticket
|
||||
|
||||
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
|
||||
super().save(force_insert=force_insert, force_update=force_update, using=using, update_fields=update_fields)
|
||||
|
||||
request = get_request()
|
||||
|
||||
if request:
|
||||
|
||||
if request.user.pk:
|
||||
|
||||
comment_user = request.user
|
||||
|
||||
else:
|
||||
|
||||
comment_user = None
|
||||
|
||||
else:
|
||||
|
||||
comment_user = None
|
||||
|
||||
|
||||
from core.models.ticket.ticket_comment import TicketComment
|
||||
|
||||
comment = TicketComment.objects.create(
|
||||
ticket = self.ticket,
|
||||
comment_type = TicketComment.CommentType.ACTION,
|
||||
body = f'linked {self}',
|
||||
source = TicketComment.CommentSource.DIRECT,
|
||||
user = comment_user,
|
||||
)
|
||||
|
||||
comment.save()
|
||||
|
@ -62,26 +62,17 @@
|
||||
<div id="data-block" class="linked-item">
|
||||
<h3>
|
||||
<div id="text">Linked Items</div>
|
||||
<div id="icons">{% include 'icons/place-holder.svg' %}{% include 'icons/place-holder.svg' %}</div>
|
||||
<div id="icons">
|
||||
<a href="{% url '_ticket_linked_item_add' ticket_type=ticket_type ticket_id=ticket.id %}">{% include 'icons/ticket/add.svg' %}</a>
|
||||
</div>
|
||||
</h3>
|
||||
<div id="item">
|
||||
An item
|
||||
</div>
|
||||
<div id="item">
|
||||
another item
|
||||
</div>
|
||||
<div id="item">
|
||||
another item
|
||||
</div>
|
||||
<div id="item">
|
||||
another item
|
||||
</div>
|
||||
<div id="item">
|
||||
another item
|
||||
</div>
|
||||
<div id="item">
|
||||
another item
|
||||
</div>
|
||||
{% if ticket.linked_items %}
|
||||
{% for linked_item in ticket.linked_items %}
|
||||
<div id="item">{{ linked_item | markdown | safe }}</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div style="text-align:center;%">Nothing found</div>
|
||||
{% endif%}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
92
app/core/views/ticket_linked_item.py
Normal file
92
app/core/views/ticket_linked_item.py
Normal file
@ -0,0 +1,92 @@
|
||||
from django.urls import reverse
|
||||
from django.views import generic
|
||||
|
||||
from django_celery_results.models import TaskResult
|
||||
|
||||
from access.mixin import OrganizationPermission
|
||||
|
||||
from core.forms.ticket_linked_item import TicketLinkedItem, TicketLinkedItemForm
|
||||
from core.views.common import AddView, ChangeView, DeleteView, IndexView
|
||||
|
||||
from settings.models.user_settings import UserSettings
|
||||
|
||||
|
||||
|
||||
class Add(AddView):
|
||||
|
||||
form_class = TicketLinkedItemForm
|
||||
|
||||
model = TicketLinkedItem
|
||||
|
||||
permission_required = [
|
||||
'itam.add_device',
|
||||
]
|
||||
|
||||
template_name = 'form.html.j2'
|
||||
|
||||
|
||||
def get_initial(self):
|
||||
|
||||
initial_values: dict = {
|
||||
'organization': UserSettings.objects.get(user = self.request.user).default_organization,
|
||||
'ticket': self.kwargs['ticket_id'],
|
||||
}
|
||||
|
||||
return initial_values
|
||||
|
||||
|
||||
def get_success_url(self, **kwargs):
|
||||
|
||||
if self.kwargs['ticket_type'] == 'request':
|
||||
|
||||
return reverse('Assistance:_ticket_request_view', args=(self.kwargs['ticket_type'],self.kwargs['ticket_id'],))
|
||||
|
||||
elif self.kwargs['ticket_type'] == 'project_task':
|
||||
|
||||
return reverse('Project Management:_project_task_view', args=(self.object.from_ticket_id.project.id, self.kwargs['ticket_type'],self.kwargs['ticket_id'],))
|
||||
|
||||
else:
|
||||
|
||||
return reverse('ITIM:_ticket_' + str(self.kwargs['ticket_type']).lower() + '_view', args=(self.kwargs['ticket_type'],self.kwargs['ticket_id'],))
|
||||
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
context['content_title'] = 'Ticket Linked Item'
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
||||
class Delete(DeleteView):
|
||||
|
||||
model = TicketLinkedItem
|
||||
|
||||
permission_required = [
|
||||
'itim.delete_cluster',
|
||||
]
|
||||
|
||||
|
||||
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):
|
||||
|
||||
if self.kwargs['ticket_type'] == 'request':
|
||||
|
||||
return reverse('Assistance:_ticket_request_view', args=(self.kwargs['ticket_type'],self.kwargs['ticket_id'],))
|
||||
|
||||
elif self.kwargs['ticket_type'] == 'project_task':
|
||||
|
||||
return reverse('Project Management:_project_task_view', args=(self.object.from_ticket_id.project.id, self.kwargs['ticket_type'],self.kwargs['ticket_id'],))
|
||||
|
||||
else:
|
||||
|
||||
return reverse('ITIM:_ticket_' + str(self.kwargs['ticket_type']).lower() + '_view', args=(self.kwargs['ticket_type'],self.kwargs['ticket_id'],))
|
@ -208,6 +208,11 @@
|
||||
width: 33%;
|
||||
}
|
||||
|
||||
#data-block.linked-item div#item p{
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
|
||||
#ticket-comments {
|
||||
padding: 10px;
|
||||
|
@ -45,6 +45,12 @@ All models must meet the following requirements:
|
||||
- If creating a new model, function `access.functions.permissions.permission_queryset()` has been updated to display the models permission(s)
|
||||
|
||||
|
||||
## Checklist
|
||||
|
||||
This section details the additional items that may need to be done when adding a new model:
|
||||
|
||||
- If the model is a primary model, add to model reference rendering in `app/core/lib/markdown_plugins/model_reference.py` function `tag_html`
|
||||
|
||||
## History
|
||||
|
||||
Currently the adding of history to a model is a manual process. edit the file located at `core.views.history` and within `View.get_object` add the model to the `switch` statement.
|
||||
|
Reference in New Issue
Block a user