From d75e9d516cb2ec2a1b66bb9f5e11497b10e722ba Mon Sep 17 00:00:00 2001 From: Jon Date: Wed, 21 Aug 2024 00:53:07 +0930 Subject: [PATCH] temp-commit --- app/app/urls.py | 9 +- app/core/forms/ticket.py | 101 ++++ app/core/models/__init__.py | 0 app/core/models/comment.py | 174 ++++++ app/core/models/ticket/__init__.py | 1 + app/core/models/ticket/change_ticket.py | 14 + app/core/models/ticket/markdown.py | 19 + app/core/models/ticket/problem_ticket.py | 14 + app/core/models/ticket/request_ticket.py | 14 + app/core/models/ticket/ticket.py | 529 +++++++++++++++++- app/core/templates/core/ticket.html.j2 | 489 ++++++++++++++++ .../templates/core/ticket/comment.html.j2 | 13 + .../templates/core/ticket/discussion.html.j2 | 11 + app/core/templatetags/markdown.py | 12 +- .../tests/unit/ticket/test_ticket_common.py | 63 +++ app/core/views/ticket.py | 82 +++ app/project-static/icons/place-holder.svg | 1 + app/templates/icons/place-holder.svg | 1 + 18 files changed, 1544 insertions(+), 3 deletions(-) create mode 100644 app/core/forms/ticket.py create mode 100644 app/core/models/__init__.py create mode 100644 app/core/models/ticket/__init__.py create mode 100644 app/core/models/ticket/change_ticket.py create mode 100644 app/core/models/ticket/markdown.py create mode 100644 app/core/models/ticket/problem_ticket.py create mode 100644 app/core/models/ticket/request_ticket.py create mode 100644 app/core/templates/core/ticket.html.j2 create mode 100644 app/core/templates/core/ticket/comment.html.j2 create mode 100644 app/core/templates/core/ticket/discussion.html.j2 create mode 100644 app/core/tests/unit/ticket/test_ticket_common.py create mode 100644 app/core/views/ticket.py create mode 100644 app/project-static/icons/place-holder.svg create mode 100644 app/templates/icons/place-holder.svg diff --git a/app/app/urls.py b/app/app/urls.py index 6577f4c9..2810628b 100644 --- a/app/app/urls.py +++ b/app/app/urls.py @@ -24,7 +24,7 @@ from .views import home -from core.views import history +from core.views import history, ticket from settings.views import user_settings @@ -48,6 +48,13 @@ path("history//", history.View.as_view(), name='_history'), re_path(r'^static/(?P.*)$', serve,{'document_root': settings.STATIC_ROOT}), + + path('ticket/', ticket.View.as_view(), name="_ticket"), + + path('ticket/add', ticket.Add.as_view(), name="_ticket"), + + path('ticket//add', ticket.Add.as_view(), name="_ticket"), + ] diff --git a/app/core/forms/ticket.py b/app/core/forms/ticket.py new file mode 100644 index 00000000..d529f94c --- /dev/null +++ b/app/core/forms/ticket.py @@ -0,0 +1,101 @@ +from django import forms +from django.db.models import Q + +from app import settings + +from core.forms.common import CommonModelForm + +from core.models.ticket.ticket import Ticket + + +class TicketForm(CommonModelForm): + + prefix = 'ticket' + + class Meta: + model = Ticket + fields = '__all__' + + + def __init__(self, *args, **kwargs): + + super().__init__(*args, **kwargs) + + self.fields['planned_start_date'].widget = forms.widgets.DateTimeInput(attrs={'type': 'datetime-local', 'format': "%Y-%m-%dT%H:%M"}) + self.fields['planned_start_date'].input_formats = settings.DATETIME_FORMAT + self.fields['planned_start_date'].format="%Y-%m-%dT%H:%M" + + self.fields['planned_finish_date'].widget = forms.widgets.DateTimeInput(attrs={'type': 'datetime-local'}) + self.fields['planned_finish_date'].input_formats = settings.DATETIME_FORMAT + self.fields['planned_finish_date'].format="%Y-%m-%dT%H:%M" + + self.fields['real_start_date'].widget = forms.widgets.DateTimeInput(attrs={'type': 'datetime-local'}) + self.fields['real_start_date'].input_formats = settings.DATETIME_FORMAT + self.fields['real_start_date'].format="%Y-%m-%dT%H:%M" + + self.fields['real_finish_date'].widget = forms.widgets.DateTimeInput(attrs={'type': 'datetime-local'}) + self.fields['real_finish_date'].input_formats = settings.DATETIME_FORMAT + self.fields['real_finish_date'].format="%Y-%m-%dT%H:%M" + + self.fields['description'].widget.attrs = {'style': "height: 800px; width: 900px"} + + # choices = kwargs.pop('camp_dates_choices', ()) + + + self.fields['opened_by'].initial = kwargs['user'].pk + + + original_fields = self.fields.copy() + ticket_type = [] + + if kwargs['initial']['ticket_type'] == 'request': + + ticket_type = self.Meta.model.fields_itsm_request + + self.fields['status'].choices = self.Meta.model.TicketStatus.Request + + elif kwargs['initial']['ticket_type'] == 'incident': + + ticket_type = self.Meta.model.fields_itsm_incident + + self.fields['status'].choices = self.Meta.model.TicketStatus.Incident + + elif kwargs['initial']['ticket_type'] == 'problem': + + ticket_type = self.Meta.model.fields_itsm_problem + + self.fields['status'].choices = self.Meta.model.TicketStatus.Problem + + elif kwargs['initial']['ticket_type'] == 'change': + + ticket_type = self.Meta.model.fields_itsm_change + + self.fields['status'].choices = self.Meta.model.TicketStatus.Change + + elif kwargs['initial']['ticket_type'] == 'issue': + + ticket_type = self.Meta.model.fields_git_issue + + self.fields['status'].choices = self.Meta.model.TicketStatus.Git + + elif kwargs['initial']['ticket_type'] == 'merge': + + ticket_type = self.Meta.model.fields_git_merge + + self.fields['status'].choices = self.Meta.model.TicketStatus.Git + + + if kwargs['user'].is_superuser: + + ticket_type += self.Meta.model.tech_fields + + # else + + # self.fields['opened_by'].widget = self.fields['opened_by'].hidden_widget() + + + for field in original_fields: + + if field not in ticket_type: + + del self.fields[field] diff --git a/app/core/models/__init__.py b/app/core/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/app/core/models/comment.py b/app/core/models/comment.py index 3b329771..c8c370fa 100644 --- a/app/core/models/comment.py +++ b/app/core/models/comment.py @@ -1,7 +1,13 @@ +from django.contrib.auth.models import User from django.db import models +from django.forms import ValidationError from access.models import TenancyObject +from change_ticket import ChangeTicket +from ticket.markdown import TicketMarkdown +from ticket.ticket import Ticket + class CommentCommonFields(models.Model): @@ -26,8 +32,11 @@ class Meta: class Comment( TenancyObject, CommentCommonFields, + TicketMarkdown, ): + # Validate comment organization is the same as the ticket. + class Meta: @@ -36,3 +45,168 @@ class Meta: 'created', 'id', ] + + verbose_name = "Comment" + + verbose_name_plural = "Comments" + + + + class Comment_ExternalSystem(models.TextChoices): # + GITHUB = '1', 'Github' + GITLAB = '2', 'Gitlab' + + + class CommentStatus(models.TextChoices): # + """ Comment Status + + Status of the Comment. By design, not all statuses are available for ALL comment types. + + ## Tasks + + """ + + TODO = '1', 'Draft' + DONE = '2', 'New' + + + + + class CommentType(models.TextChoices): + """Type of the ticket""" + + COMMENT = '1', 'Comment' + TASK = '2', 'Task' + SOLUTION = '3', 'Solution' + + + parent = models.ForeignKey( + 'self', + blank= True, + default = None, + help_text = 'Parent comment this comment creates a discussion with', + null = True, + on_delete=models.CASCADE, + verbose_name = 'Parent Comment', + ) + + + comment_type = models.IntegerField( + blank = False, + choices=CommentType, + default=None, + help_text = 'The type of comment this is', + null=True, + verbose_name = 'Type', + ) + + + ticket = models.ForeignKey( + Ticket, + blank= True, + default = None, + help_text = 'Parent comment this comment creates a discussion with', + null = True, + on_delete=models.CASCADE, + verbose_name = 'Parent Comment', + ) + + + external_ref = models.IntegerField( + blank = True, + default=None, + help_text = 'External System reference', + null=True, + verbose_name = 'Reference Number', + ) # external reference or null. i.e. github comment number + + + external_system = models.IntegerField( + blank = True, + choices=Comment_ExternalSystem, + default=None, + help_text = 'External system this item derives', + null=True, + verbose_name = 'External System', + ) + + + # user + + + body = models.TextField( + blank = True, + default = None, + help_text = 'Body of Comment', + null = False, + verbose_name = 'Body', + ) # text, markdown. validate to ensure a saved comment that does nothing creates nothing + + + # Duration + + + # is_private + + + status = models.IntegerField( # will require validation by ticket type as status for types will be different + blank = False, + choices=CommentStatus, + default=None, + help_text = 'Status of Comment', + null=True, + verbose_name = 'Status', + ) + + + date_closed = models.DateTimeField( + blank = True, + help_text = 'Date closed', + null = True, + verbose_name = 'Closed Date', + ) + + + + # + # ITSM Fields + # + + + # Category + + # is_template + + # source # helpdesk|direct|phone|email + + # responsible_user # user who task is for + + # scheduled task # when the task is scheduled for + + + + # + # Project Management fields + # + + # planned start date + + # planned finish date + + # planned duration + + # actual start date + + # actual finish date + + + + # def __str__(self): # ??????????? what will this need to be + + # return self.name + + + @property + def markdown_comment(self) -> str: + + return self.render_markdown(self.body) diff --git a/app/core/models/ticket/__init__.py b/app/core/models/ticket/__init__.py new file mode 100644 index 00000000..b9742821 --- /dev/null +++ b/app/core/models/ticket/__init__.py @@ -0,0 +1 @@ +from . import * \ No newline at end of file diff --git a/app/core/models/ticket/change_ticket.py b/app/core/models/ticket/change_ticket.py new file mode 100644 index 00000000..3f9d5e8f --- /dev/null +++ b/app/core/models/ticket/change_ticket.py @@ -0,0 +1,14 @@ +from django.forms import ValidationError + + +class ChangeTicket: + + + @property + def validate_change_ticket(self): + + # check status + + # check type + + pass diff --git a/app/core/models/ticket/markdown.py b/app/core/models/ticket/markdown.py new file mode 100644 index 00000000..e845a680 --- /dev/null +++ b/app/core/models/ticket/markdown.py @@ -0,0 +1,19 @@ + + + +class TicketMarkdown: + """Ticket and Comment markdown functions + + Intended to be used for all areas of a tickets, projects and comments. + """ + + + def render_markdown(self, markdown): + + # Requires context of ticket for ticket markdown + + # Requires context of ticket for comment + + # requires context of project for project task comment + + pass diff --git a/app/core/models/ticket/problem_ticket.py b/app/core/models/ticket/problem_ticket.py new file mode 100644 index 00000000..eb4dd7a2 --- /dev/null +++ b/app/core/models/ticket/problem_ticket.py @@ -0,0 +1,14 @@ +from django.forms import ValidationError + + +class ProblemTicket: + + + @property + def validate_problem_ticket(self): + + # check status + + # check type + + pass diff --git a/app/core/models/ticket/request_ticket.py b/app/core/models/ticket/request_ticket.py new file mode 100644 index 00000000..05cb7809 --- /dev/null +++ b/app/core/models/ticket/request_ticket.py @@ -0,0 +1,14 @@ +from django.forms import ValidationError + + +class RequestTicket: + + + @property + def validate_request_ticket(self): + + # check status + + # check type + + pass diff --git a/app/core/models/ticket/ticket.py b/app/core/models/ticket/ticket.py index e80ef5c6..470d8296 100644 --- a/app/core/models/ticket/ticket.py +++ b/app/core/models/ticket/ticket.py @@ -1,6 +1,97 @@ +from django.contrib.auth.models import User from django.db import models +from django.forms import ValidationError + +from access.fields import AutoCreatedField +from access.models import TenancyObject, Team + +from .change_ticket import ChangeTicket +from .markdown import TicketMarkdown +from .problem_ticket import ProblemTicket +from .request_ticket import RequestTicket + + + +class TicketValues: + + + _DRAFT_INT = '1' + _NEW_INT = '2' + + _ASSIGNED_INT = '3' + _CLOSED_INT = '4' + _INVALID_INT = '5' + + # + # ITSM statuses + # + + # Requests / Incidents / Problems / Changed + _ASSIGNED_PLANNING_INT = '6' + _PENDING_INT = '7' + + # Requests / Incidents / Problems + _SOLVED_INT = '8' + + # Problem + + _OBSERVATION_INT = '9' + + # Problems / Changes + + _ACCEPTED_INT = '10' + + # Changes + + _EVALUATION_INT = '11' + _APPROVALS_INT = '12' + _TESTING_INT = '13' + _QUALIFICATION_INT = '14' + _APPLIED_INT = '15' + _REVIEW_INT = '16' + _CANCELLED_INT = '17' + _REFUSED_INT = '18' + + + + + _DRAFT_STR = 'Drafta' + _NEW_STR = 'New' + + _ASSIGNED_STR = 'Assigned' + _CLOSED_STR = 'Closed' + _INVALID_STR = 'Invalid' + + # + # ITSM statuses + # + + # Requests / Incidents / Problems / Changed + _ASSIGNED_PLANNING_STR = 'Assigned (Planning)' + _PENDING_STR = 'Pending' + + # Requests / Incidents / Problems + _SOLVED_STR = 'Solved' + + # Problem + + _OBSERVATION_STR = 'Under Observation' + + # Problems / Changes + + _ACCEPTED_STR = 'Accepted' + + # Changes + + _EVALUATION_STR = 'Evaluation' + _APPROVALS_STR = 'Approvals' + _TESTING_STR = 'Testing' + _QUALIFICATION_STR = 'Qualification' + _APPLIED_STR = 'Applied' + _REVIEW_STR = 'Review' + _CANCELLED_STR = 'Cancelled' + _REFUSED_STR = 'Refused' -from access.models import TenancyObject @@ -26,6 +117,10 @@ class Meta: class Ticket( TenancyObject, TicketCommonFields, + ChangeTicket, + ProblemTicket, + RequestTicket, + TicketMarkdown, ): @@ -35,3 +130,435 @@ class Meta: 'id' ] + verbose_name = "Ticket" + + verbose_name_plural = "Tickets" + + + + class Ticket_ExternalSystem(models.TextChoices): # + GITHUB = '1', 'Github' + GITLAB = '2', 'Gitlab' + + + + + + # class TicketStatus(models.TextChoices): # + class TicketStatus: # + """ Ticket Status + + Status of the ticket. By design, not all statuses are available for ALL ticket types. + + ## Request / Incident ticket + + - Draft + - New + - Assigned + - Assigned (Planned) + - Pending + - Solved + - Closed + + + ## Problem Ticket + + - Draft + - New + - Accepted + - Assigned + - Assigned (Planned) + - Pending + - Solved + - Under Observation + - Closed + + ## Change Ticket + + - Draft + - New + - Evaluation + - Approvals + - Accepted + - Pending + - Testing + - Qualification + - Applied + - Review + - Closed + - Cancelled + - Refused + + """ + + # DRAFT = TicketValues._DRAFT_INT, TicketValues._DRAFT_STR + # NEW = TicketValues._NEW_INT, TicketValues._NEW_STR + + # ASSIGNED = TicketValues._ASSIGNED_INT, TicketValues._ASSIGNED_STR + # CLOSED = TicketValues._CLOSED_INT, TicketValues._CLOSED_STR + # INVALID = TicketValues._INVALID_INT, TicketValues._INVALID_STR + + # # + # # ITSM statuses + # # + + # # Requests / Incidents / Problems / Changed + # ASSIGNED_PLANNING = TicketValues._ASSIGNED_PLANNING_INT, TicketValues._ASSIGNED_PLANNING_STR + # PENDING = TicketValues._PENDING_INT, TicketValues._PENDING_STR + + # # Requests / Incidents / Problems + # SOLVED = TicketValues._SOLVED_INT, TicketValues._SOLVED_STR + + # # Problem + + # OBSERVATION = TicketValues._OBSERVATION_INT, TicketValues._OBSERVATION_STR + + # # Problems / Changes + + # ACCEPTED = TicketValues._ACCEPTED_INT, TicketValues._ACCEPTED_STR + + # # Changes + + # EVALUATION = TicketValues._EVALUATION_INT, TicketValues._EVALUATION_STR + # APPROVALS = TicketValues._APPROVALS_INT, TicketValues._APPROVALS_STR + # TESTING = TicketValues._TESTING_INT, TicketValues._TESTING_STR + # QUALIFICATION = TicketValues._QUALIFICATION_INT, TicketValues._QUALIFICATION_STR + # APPLIED = TicketValues._APPLIED_INT, TicketValues._APPLIED_STR + # REVIEW = TicketValues._REVIEW_INT, TicketValues._REVIEW_STR + # CANCELLED = TicketValues._CANCELLED_INT, TicketValues._CANCELLED_STR + # REFUSED = TicketValues._REFUSED_INT, TicketValues._REFUSED_STR + + + class Request(models.TextChoices): # + + DRAFT = TicketValues._DRAFT_INT, TicketValues._DRAFT_STR + NEW = TicketValues._NEW_INT, TicketValues._NEW_STR + ASSIGNED = TicketValues._ASSIGNED_INT, TicketValues._ASSIGNED_STR + ASSIGNED_PLANNING = TicketValues._ASSIGNED_PLANNING_INT, TicketValues._ASSIGNED_PLANNING_STR + PENDING = TicketValues._PENDING_INT, TicketValues._PENDING_STR + SOLVED = TicketValues._SOLVED_INT, TicketValues._SOLVED_STR + CLOSED = TicketValues._CLOSED_INT, TicketValues._CLOSED_STR + INVALID = TicketValues._INVALID_INT, TicketValues._INVALID_STR + + + + class Incident(models.TextChoices): # + + DRAFT = TicketValues._DRAFT_INT, TicketValues._DRAFT_STR + NEW = TicketValues._NEW_INT, TicketValues._NEW_STR + ASSIGNED = TicketValues._ASSIGNED_INT, TicketValues._ASSIGNED_STR + ASSIGNED_PLANNING = TicketValues._ASSIGNED_PLANNING_INT, TicketValues._ASSIGNED_PLANNING_STR + PENDING = TicketValues._PENDING_INT, TicketValues._PENDING_STR + SOLVED = TicketValues._SOLVED_INT, TicketValues._SOLVED_STR + CLOSED = TicketValues._CLOSED_INT, TicketValues._CLOSED_STR + INVALID = TicketValues._INVALID_INT, TicketValues._INVALID_STR + + + + class Problem(models.TextChoices): # + + DRAFT = TicketValues._DRAFT_INT, TicketValues._DRAFT_STR + NEW = TicketValues._NEW_INT, TicketValues._NEW_STR + ACCEPTED = TicketValues._ACCEPTED_INT, TicketValues._ACCEPTED_STR + ASSIGNED = TicketValues._ASSIGNED_INT, TicketValues._ASSIGNED_STR + ASSIGNED_PLANNING = TicketValues._ASSIGNED_PLANNING_INT, TicketValues._ASSIGNED_PLANNING_STR + PENDING = TicketValues._PENDING_INT, TicketValues._PENDING_STR + SOLVED = TicketValues._SOLVED_INT, TicketValues._SOLVED_STR + OBSERVATION = TicketValues._OBSERVATION_INT, TicketValues._OBSERVATION_STR + CLOSED = TicketValues._CLOSED_INT, TicketValues._CLOSED_STR + INVALID = TicketValues._INVALID_INT, TicketValues._INVALID_STR + + + + class Change(models.TextChoices): # + + DRAFT = TicketValues._DRAFT_INT, TicketValues._DRAFT_STR + NEW = TicketValues._NEW_INT, TicketValues._NEW_STR + EVALUATION = TicketValues._EVALUATION_INT, TicketValues._EVALUATION_STR + APPROVALS = TicketValues._APPROVALS_INT, TicketValues._APPROVALS_STR + ACCEPTED = TicketValues._ACCEPTED_INT, TicketValues._ACCEPTED_STR + PENDING = TicketValues._PENDING_INT, TicketValues._PENDING_STR + TESTING = TicketValues._TESTING_INT, TicketValues._TESTING_STR + QUALIFICATION = TicketValues._QUALIFICATION_INT, TicketValues._QUALIFICATION_STR + APPLIED = TicketValues._APPLIED_INT, TicketValues._APPLIED_STR + REVIEW = TicketValues._REVIEW_INT, TicketValues._REVIEW_STR + CLOSED = TicketValues._CLOSED_INT, TicketValues._CLOSED_STR + CANCELLED = TicketValues._CANCELLED_INT, TicketValues._CANCELLED_STR + REFUSED = TicketValues._REFUSED_INT, TicketValues._REFUSED_STR + + + class Git(models.TextChoices): # + + DRAFT = TicketValues._DRAFT_INT, TicketValues._DRAFT_STR + NEW = TicketValues._NEW_INT, TicketValues._NEW_STR + ASSIGNED = TicketValues._ASSIGNED_INT, TicketValues._ASSIGNED_STR + ASSIGNED_PLANNING = TicketValues._ASSIGNED_PLANNING_INT, TicketValues._ASSIGNED_PLANNING_STR + CLOSED = TicketValues._CLOSED_INT, TicketValues._CLOSED_STR + INVALID = TicketValues._INVALID_INT, TicketValues._INVALID_STR + + + + + class TicketType(models.TextChoices): + """Type of the ticket""" + + REQUEST = '1', 'Request' + INCIDENT = '2', 'Incident' + CHANGE = '3', 'Change' + PROBLEM = '4', 'Problem' + ISSUE = '5', 'Issue' + MERGE_REQUEST = '6', 'Merge Request' + + + + def validation_title(field): + + if not field: + raise ValueError + + + status = models.IntegerField( # will require validation by ticket type as status for types will be different + blank = False, + choices=TicketStatus.Request, + default = TicketStatus.Request.NEW, + help_text = 'Status of ticket', + null=True, + verbose_name = 'Status', + ) + + category = models.CharField( + blank = False, + help_text = "Category of the Ticket", + max_length = 50, + unique = True, + verbose_name = 'Category', + ) + + title = models.CharField( + blank = False, + help_text = "Title of the Ticket", + max_length = 50, + unique = True, + verbose_name = 'Title', + ) + + description = models.TextField( + blank = False, + default = None, + help_text = 'Ticket Description', + null = False, + verbose_name = 'Description', + ) # text, markdown + + external_ref = models.IntegerField( + blank = True, + default=None, + help_text = 'External System reference', + null=True, + verbose_name = 'Reference Number', + ) # external reference or null. i.e. github issue number + + + external_system = models.IntegerField( + blank = True, + choices=Ticket_ExternalSystem, + default=None, + help_text = 'External system this item derives', + null=True, + verbose_name = 'External System', + ) + + + ticket_type = models.IntegerField( + blank = False, + choices=TicketType, + default=None, + help_text = 'The type of ticket this is', + null=True, + verbose_name = 'Type', + ) + + + + # date_opened = models.DateTimeField( # created + # verbose_name = 'Open Date', + # null = True, + # blank = True + # ) + + + date_closed = models.DateTimeField( + blank = True, + help_text = 'Date ticket closed', + null = True, + verbose_name = 'Closed Date', + ) + + + opened_by = models.ForeignKey( + User, + blank= False, + # default = True, + help_text = 'Who is the ticket for', + null = False, + on_delete = models.DO_NOTHING, + related_name = 'opened_by', + verbose_name = 'Opened By', + ) + + subscribed_users = models.ManyToManyField( + User, + blank= True, + default = True, + help_text = 'Subscribe a User(s) to the ticket to receive updates', + related_name = 'subscribed_users', + symmetrical = False, + # null = True, + verbose_name = 'Subscribed User(s)', + ) + + subscribed_teams = models.ManyToManyField( + Team, + blank= True, + default = True, + help_text = 'Subscribe a Team(s) to the ticket to receive updates', + related_name = 'subscribed_teams', + symmetrical = False, + # null = True, + verbose_name = 'Subscribed Team(s)', + ) + + assigned_users = models.ManyToManyField( + User, + blank= True, + default = True, + help_text = 'Assign the ticket to a User(s)', + related_name = 'assigned_users', + symmetrical = False, + # null = True, + verbose_name = 'Assigned User(s)', + ) + + assigned_teams = models.ManyToManyField( + Team, + blank= True, + default = True, + help_text = 'Assign the ticket to a Team(s)', + related_name = 'assigned_teams', + symmetrical = False, + # null = True, + verbose_name = 'Assigned Team(s)', + ) + + urgency = models.IntegerField( + blank = True, + # choices=TicketType, + default=None, + help_text = 'How urgent is this tickets resolution?', + null=True, + verbose_name = 'Urgency', + ) + + impact = models.IntegerField( + blank = True, + # choices=TicketType, + default=None, + help_text = 'End user assessed impact', + null=True, + verbose_name = 'Impact', + ) + + priority = models.IntegerField( + blank = True, + # choices=TicketType, + default=None, + help_text = 'What priority should this ticket for its completion', + null=True, + verbose_name = 'Priority', + ) + + + planned_start_date = models.DateTimeField( + blank = True, + help_text = 'Planned start date.', + null = True, + verbose_name = 'Planned Start Date', + ) + + planned_finish_date = models.DateTimeField( + blank = True, + help_text = 'Planned finish date', + null = True, + verbose_name = 'Planned Finish Date', + ) + + real_start_date = models.DateTimeField( + blank = True, + help_text = 'Real start date', + null = True, + verbose_name = 'Real Start Date', + ) + + real_finish_date = models.DateTimeField( + blank = True, + help_text = 'Real finish date', + null = True, + verbose_name = 'Real Finish Date', + ) + + + + # ?? date_edit date of last edit + + # def __str__(self): # ??????????? what will this need to be + + # return self.name + + common_fields: list(str()) = [ + 'title', + 'description', + 'opened_by', + ] + + common_itsm_fields: list(str()) = common_fields + [ + 'urgency', + + ] + + fields_itsm_request: list(str()) = common_itsm_fields + [] + + fields_itsm_incident: list(str()) = common_itsm_fields + [] + + fields_itsm_problem: list(str()) = common_itsm_fields + [] + + fields_itsm_change: list(str()) = common_itsm_fields + [] + + + common_git_fields: list(str()) = common_fields + [ + + ] + + fields_git_issue: list(str()) = common_fields + [ + + ] + + fields_git_merge_request: list(str()) = common_fields + [ + + ] + + tech_fields = [ + 'category', + 'assigned_teams', + 'assigned_users', + 'subscribed_teams', + 'subscribed_users', + 'status', + 'impact', + 'priority', + ] + + + @property + def markdown_description(self) -> str: + + return self.render_markdown(self.description) diff --git a/app/core/templates/core/ticket.html.j2 b/app/core/templates/core/ticket.html.j2 new file mode 100644 index 00000000..89b64d3b --- /dev/null +++ b/app/core/templates/core/ticket.html.j2 @@ -0,0 +1,489 @@ +{% extends 'base.html.j2' %} + +{% load markdown %} + +{% block title %}Ticket{% endblock %} + +{% block article %} + + + + + +
+
+
+ the ticket text + + swagger_docs + + sdfdfsdfds + sdfdfsdfds + + + + + ghgf + + fg + +
+ +
+ +
+

+
John Smith wrote on xx Aug 2024
+
{% include 'icons/place-holder.svg' %}{% include 'icons/place-holder.svg' %}
+

+
+ An item +
+
+ another item +
+
+ +
+

+
Linked Items
+
{% include 'icons/place-holder.svg' %}{% include 'icons/place-holder.svg' %}
+

+
+ An item +
+
+ another item +
+
+ another item +
+
+ another item +
+
+ another item +
+
+ another item +
+
+ +
+ +
+
    +
  • + {% include 'core/ticket/comment.html.j2' %} +
  • +
  • John smith add x as related to this ticket
  • +
  • + + {% include 'core/ticket/comment.html.j2' %} + + {% include 'core/ticket/discussion.html.j2' %} + + +
  • +
  • Jane smith mentioned this ticket in xx
  • +
  • sdasfdgdfgdfg dfg dfg dfg d
  • +
+
+ +
+ + +
+

Incident

+ metadata +
+ + +
+{% endblock %} \ No newline at end of file diff --git a/app/core/templates/core/ticket/comment.html.j2 b/app/core/templates/core/ticket/comment.html.j2 new file mode 100644 index 00000000..f5e839fa --- /dev/null +++ b/app/core/templates/core/ticket/comment.html.j2 @@ -0,0 +1,13 @@ +{% load markdown %} + +
+

+
Jon Smith wrote on xx Aug 2024
+
{% include 'icons/place-holder.svg' %}{% include 'icons/place-holder.svg' %}
+ +

+
+ a comment +
+ +
diff --git a/app/core/templates/core/ticket/discussion.html.j2 b/app/core/templates/core/ticket/discussion.html.j2 new file mode 100644 index 00000000..53b85a34 --- /dev/null +++ b/app/core/templates/core/ticket/discussion.html.j2 @@ -0,0 +1,11 @@ + +
+ + +
Discussion
+
{% include 'icons/place-holder.svg' %}
+
+ + {% include 'core/ticket/comment.html.j2' %} + +
diff --git a/app/core/templatetags/markdown.py b/app/core/templatetags/markdown.py index d303dcc7..61945a25 100644 --- a/app/core/templatetags/markdown.py +++ b/app/core/templatetags/markdown.py @@ -9,4 +9,14 @@ @register.filter() @stringfilter def markdown(value): - return md.markdown(value, extensions=['markdown.extensions.fenced_code']) \ No newline at end of file + return md.markdown(value, extensions=['markdown.extensions.fenced_code']) + + +@register.filter() +@stringfilter +def count_lines(value) -> int: + print(f'value: {value}') + val = str(value).split('\\n') + print(f'lines: {len(val)}') + return len(val) + diff --git a/app/core/tests/unit/ticket/test_ticket_common.py b/app/core/tests/unit/ticket/test_ticket_common.py new file mode 100644 index 00000000..3a336da5 --- /dev/null +++ b/app/core/tests/unit/ticket/test_ticket_common.py @@ -0,0 +1,63 @@ +import pytest +import unittest +import requests + +from django.test import TestCase + +from app.tests.abstract.models import ModelDisplay, ModelIndex + + + +class TicketCommon( + TestCase +): + + def text_ticket_field_type_opened_by(self): + """Ensure field is of a certain type + + opened_by_field must be of type int + """ + pass + + def text_ticket_field_value_not_null_opened_by(self): + """Ensure field is not null + + opened_by_field must be set and not null + """ + pass + + + def text_ticket_field_value_auto_set_opened_by(self): + """Ensure field is auto set within code + + opened_by_field must be set by code with non-tech user not being able to change + """ + pass + + + def text_ticket_field_value_tech_set_opened_by(self): + """Ensure field can be set by a technician + + opened_by_field can be set by a technician + """ + pass + + + + def text_ticket_type_fields(self): + """Placeholder test + + following tests to be written: + + - only tech can change tech fields (same org) + - non-tech cant see tech fields (same org) during creation + - non-tech cant change tech fields (same org) + - only tech can change tech fields (different org) + - non-tech cant see tech fields (different org) during creation + - non-tech cant change tech fields (different org) + + - itsm ticket has the itsm related fields + - non-itsm ticket does not have any itsm related fields + + """ + pass diff --git a/app/core/views/ticket.py b/app/core/views/ticket.py new file mode 100644 index 00000000..d55e067f --- /dev/null +++ b/app/core/views/ticket.py @@ -0,0 +1,82 @@ +import markdown + +from django.views import generic + +from django_celery_results.models import TaskResult + +from access.mixin import OrganizationPermission + +from core.forms.ticket import TicketForm +from core.models.ticket.ticket import Ticket +from core.views.common import AddView, ChangeView, DeleteView, IndexView + +from settings.models.user_settings import UserSettings + + +class Add(AddView): + + form_class = TicketForm + + model = Ticket + permission_required = [ + 'itam.add_device', + ] + template_name = 'form.html.j2' + + + def get_initial(self): + return { + 'organization': UserSettings.objects.get(user = self.request.user).default_organization, + # 'status': self.model.TicketStatus.NEW, + 'ticket_type': self.kwargs['ticket_type'], + } + + def form_valid(self, form): + form.instance.is_global = False + return super().form_valid(form) + + + def get_success_url(self, **kwargs): + + return f"/ticket/" + + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context['content_title'] = 'New Ticket' + + return context + + + +class View(OrganizationPermission, generic.ListView): + + context_object_name = "task_result" + + fields = [ + "task_id", + 'task_name', + 'status', + 'task_args', + ] + + model = TaskResult + + permission_required = [ + 'django_celery_results.view_taskresult', + ] + + template_name = 'core/ticket.html.j2' + + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context['content_title'] = f"Task " + + return context + + + # def post(self, request, *args, **kwargs): + # pass diff --git a/app/project-static/icons/place-holder.svg b/app/project-static/icons/place-holder.svg new file mode 100644 index 00000000..312a10e7 --- /dev/null +++ b/app/project-static/icons/place-holder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/templates/icons/place-holder.svg b/app/templates/icons/place-holder.svg new file mode 100644 index 00000000..312a10e7 --- /dev/null +++ b/app/templates/icons/place-holder.svg @@ -0,0 +1 @@ + \ No newline at end of file