From 94c5b8ed37d56cb8d4e5d3e25756e5c788d64d80 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 14 Sep 2024 16:06:20 +0930 Subject: [PATCH] feat(project_management): Add project milestones ref: #285 --- app/core/forms/ticket.py | 12 ++ app/core/forms/validate_ticket.py | 2 + ...tcomment_ticketcommentcategory_and_more.py | 7 +- ...et_milestone_ticket_opened_by_and_more.py} | 17 +- app/core/models/ticket/ticket.py | 14 +- app/core/templates/core/ticket.html.j2 | 14 +- app/core/tests/unit/ticket/test_ticket.py | 36 +++- app/project_management/forms/project.py | 5 + .../forms/project_milestone.py | 109 +++++++++++ .../migrations/0001_initial.py | 23 ++- .../models/project_milestone.py | 74 +++++++ .../project_management/project.html.j2 | 33 ++++ .../project_milestone.html.j2 | 79 ++++++++ app/project_management/urls.py | 7 +- app/project_management/views/project.py | 6 +- .../views/project_milestones.py | 182 ++++++++++++++++++ 16 files changed, 606 insertions(+), 14 deletions(-) rename app/core/migrations/{0006_ticket_project_ticket_subscribed_teams_and_more.py => 0006_ticket_milestone_ticket_opened_by_and_more.py} (88%) create mode 100644 app/project_management/forms/project_milestone.py create mode 100644 app/project_management/models/project_milestone.py create mode 100644 app/project_management/templates/project_management/project_milestone.html.j2 create mode 100644 app/project_management/views/project_milestones.py diff --git a/app/core/forms/ticket.py b/app/core/forms/ticket.py index f2a5a71fe..f6849829d 100644 --- a/app/core/forms/ticket.py +++ b/app/core/forms/ticket.py @@ -52,6 +52,18 @@ def __init__(self, request, *args, **kwargs): self.fields['ticket_type'].widget = self.fields['ticket_type'].hidden_widget() self.fields['organization'].widget = self.fields['organization'].hidden_widget() + if self.instance.project is not None: + + self.fields['milestone'].queryset = self.fields['milestone'].queryset.filter( + project=self.instance.project + ) + + else: + + self.fields['milestone'].queryset = self.fields['milestone'].queryset.filter( + id=0 + ) + original_fields = self.fields.copy() ticket_type = [] diff --git a/app/core/forms/validate_ticket.py b/app/core/forms/validate_ticket.py index a50247e5a..ffd454947 100644 --- a/app/core/forms/validate_ticket.py +++ b/app/core/forms/validate_ticket.py @@ -49,6 +49,7 @@ class TicketValidation( 'planned_finish_date', 'priority', 'project', + 'milestone', 'real_start_date', 'real_finish_date', 'subscribed_users', @@ -67,6 +68,7 @@ class TicketValidation( 'planned_finish_date', 'priority', 'project', + 'milestone', 'real_start_date', 'real_finish_date', 'subscribed_users', diff --git a/app/core/migrations/0005_ticketcategory_ticketcomment_ticketcommentcategory_and_more.py b/app/core/migrations/0005_ticketcategory_ticketcomment_ticketcommentcategory_and_more.py index 9ad5d5d0d..ed57713c6 100644 --- a/app/core/migrations/0005_ticketcategory_ticketcomment_ticketcommentcategory_and_more.py +++ b/app/core/migrations/0005_ticketcategory_ticketcomment_ticketcommentcategory_and_more.py @@ -1,7 +1,8 @@ -# Generated by Django 5.0.8 on 2024-09-13 05:06 +# Generated by Django 5.0.8 on 2024-09-14 06:29 import access.fields import access.models +import core.lib.slash_commands import core.models.ticket.ticket import core.models.ticket.ticket_comment import django.db.models.deletion @@ -66,6 +67,7 @@ class Migration(migrations.Migration): 'verbose_name_plural': 'Comments', 'ordering': ['ticket', 'parent_id'], }, + bases=(core.lib.slash_commands.SlashCommands, models.Model), ), migrations.CreateModel( name='TicketCommentCategory', @@ -121,8 +123,6 @@ class Migration(migrations.Migration): ('real_finish_date', models.DateTimeField(blank=True, help_text='Real finish date', null=True, verbose_name='Real Finish Date')), ('assigned_teams', models.ManyToManyField(blank=True, help_text='Assign the ticket to a Team(s)', related_name='assigned_teams', to='access.team', verbose_name='Assigned Team(s)')), ('assigned_users', models.ManyToManyField(blank=True, help_text='Assign the ticket to a User(s)', related_name='assigned_users', to=settings.AUTH_USER_MODEL, verbose_name='Assigned User(s)')), - ('opened_by', models.ForeignKey(help_text='Who is the ticket for', on_delete=django.db.models.deletion.DO_NOTHING, related_name='opened_by', to=settings.AUTH_USER_MODEL, verbose_name='Opened By')), - ('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])), ], options={ 'verbose_name': 'Ticket', @@ -130,5 +130,6 @@ class Migration(migrations.Migration): 'ordering': ['id'], 'permissions': [('add_ticket_request', 'Can add a request ticket'), ('change_ticket_request', 'Can change any request ticket'), ('delete_ticket_request', 'Can delete a request ticket'), ('import_ticket_request', 'Can import a request ticket'), ('purge_ticket_request', 'Can purge a request ticket'), ('triage_ticket_request', 'Can triage all request ticket'), ('view_ticket_request', 'Can view all request ticket'), ('add_ticket_incident', 'Can add a incident ticket'), ('change_ticket_incident', 'Can change any incident ticket'), ('delete_ticket_incident', 'Can delete a incident ticket'), ('import_ticket_incident', 'Can import a incident ticket'), ('purge_ticket_incident', 'Can purge a incident ticket'), ('triage_ticket_incident', 'Can triage all incident ticket'), ('view_ticket_incident', 'Can view all incident ticket'), ('add_ticket_problem', 'Can add a problem ticket'), ('change_ticket_problem', 'Can change any problem ticket'), ('delete_ticket_problem', 'Can delete a problem ticket'), ('import_ticket_problem', 'Can import a problem ticket'), ('purge_ticket_problem', 'Can purge a problem ticket'), ('triage_ticket_problem', 'Can triage all problem ticket'), ('view_ticket_problem', 'Can view all problem ticket'), ('add_ticket_change', 'Can add a change ticket'), ('change_ticket_change', 'Can change any change ticket'), ('delete_ticket_change', 'Can delete a change ticket'), ('import_ticket_change', 'Can import a change ticket'), ('purge_ticket_change', 'Can purge a change ticket'), ('triage_ticket_change', 'Can triage all change ticket'), ('view_ticket_change', 'Can view all change ticket'), ('add_ticket_project_task', 'Can add a project task'), ('change_ticket_project_task', 'Can change any project task'), ('delete_ticket_project_task', 'Can delete a project task'), ('import_ticket_project_task', 'Can import a project task'), ('purge_ticket_project_task', 'Can purge a project task'), ('triage_ticket_project_task', 'Can triage all project task'), ('view_ticket_project_task', 'Can view all project task')], }, + bases=(core.lib.slash_commands.SlashCommands, models.Model), ), ] diff --git a/app/core/migrations/0006_ticket_project_ticket_subscribed_teams_and_more.py b/app/core/migrations/0006_ticket_milestone_ticket_opened_by_and_more.py similarity index 88% rename from app/core/migrations/0006_ticket_project_ticket_subscribed_teams_and_more.py rename to app/core/migrations/0006_ticket_milestone_ticket_opened_by_and_more.py index 834f0003f..685b3f18e 100644 --- a/app/core/migrations/0006_ticket_project_ticket_subscribed_teams_and_more.py +++ b/app/core/migrations/0006_ticket_milestone_ticket_opened_by_and_more.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.8 on 2024-09-13 05:06 +# Generated by Django 5.0.8 on 2024-09-14 06:29 import access.models import core.models.ticket.ticket_comment @@ -18,6 +18,21 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AddField( + model_name='ticket', + name='milestone', + field=models.ForeignKey(blank=True, help_text='Assign to a milestone', null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='project_management.projectmilestone', verbose_name='Project Milestone'), + ), + migrations.AddField( + model_name='ticket', + name='opened_by', + field=models.ForeignKey(help_text='Who is the ticket for', on_delete=django.db.models.deletion.DO_NOTHING, related_name='opened_by', to=settings.AUTH_USER_MODEL, verbose_name='Opened By'), + ), + migrations.AddField( + model_name='ticket', + name='organization', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists]), + ), migrations.AddField( model_name='ticket', name='project', diff --git a/app/core/models/ticket/ticket.py b/app/core/models/ticket/ticket.py index 4944e8058..0e7e73a35 100644 --- a/app/core/models/ticket/ticket.py +++ b/app/core/models/ticket/ticket.py @@ -11,7 +11,7 @@ from core.middleware.get_request import get_request from core.models.ticket.ticket_category import TicketCategory -from project_management.models.projects import Project +from project_management.models.project_milestone import Project, ProjectMilestone @@ -528,6 +528,15 @@ def validation_title(field): verbose_name = 'Project', ) + milestone = models.ForeignKey( + ProjectMilestone, + blank= True, + help_text = 'Assign to a milestone', + null = True, + on_delete = models.DO_NOTHING, + verbose_name = 'Project Milestone', + ) + opened_by = models.ForeignKey( User, @@ -642,6 +651,7 @@ def __str__(self): 'category' 'urgency', 'project', + 'milestone', 'priority', 'impact', 'subscribed_teams', @@ -680,6 +690,7 @@ def __str__(self): fields_project_task: list(str()) = common_fields + [ 'category', + 'milestone', 'status', 'urgency', 'priority', @@ -695,6 +706,7 @@ def __str__(self): tech_fields = [ 'category', 'project', + 'milestone', 'assigned_users', 'assigned_teams', 'subscribed_teams', diff --git a/app/core/templates/core/ticket.html.j2 b/app/core/templates/core/ticket.html.j2 index 0e02ca135..c18fd6868 100644 --- a/app/core/templates/core/ticket.html.j2 +++ b/app/core/templates/core/ticket.html.j2 @@ -132,16 +132,22 @@ {% endif %} + {% if ticket.project %}
- {% if ticket.project %} {{ ticket.project }} - {% else %} - - - {% endif %}
+ {% endif %} + {% if ticket.milestone %} +
+ + + {{ ticket.milestone }} + +
+ {% endif %}
U{{ ticket.get_urgency_display }} / I{{ ticket.get_impact_display }} / P{{ ticket.get_priority_display }} diff --git a/app/core/tests/unit/ticket/test_ticket.py b/app/core/tests/unit/ticket/test_ticket.py index 468875e0b..a9e663533 100644 --- a/app/core/tests/unit/ticket/test_ticket.py +++ b/app/core/tests/unit/ticket/test_ticket.py @@ -54,4 +54,38 @@ def test_ticket_create_add_opened_by_as_watcher_api(self): as a subscribed user. """ - pass \ No newline at end of file + pass + + + @pytest.mark.skip(reason='test to be written') + def test_field_milestone_no_project(self): + """Field Value Test + + Ensure that a milestone can't be applied if no project + has been selected + """ + + pass + + + @pytest.mark.skip(reason='test to be written') + def test_field_milestone_has_project(self): + """Field Value Test + + Ensure that a milestone can be applied if a project + has been selected + """ + + pass + + + @pytest.mark.skip(reason='test to be written') + def test_field_milestone_different_project(self): + """Field Value Test + + Ensure that a milestone from a different project + can't be applied + """ + + pass + diff --git a/app/project_management/forms/project.py b/app/project_management/forms/project.py index 61a9b5c18..49187801c 100644 --- a/app/project_management/forms/project.py +++ b/app/project_management/forms/project.py @@ -97,6 +97,11 @@ class DetailForm(ProjectForm): "slug": "tasks", "sections": [] }, + "milestones": { + "name": "Milestones", + "slug": "milestones", + "sections": [] + }, "notes": { "name": "Notes", "slug": "notes", diff --git a/app/project_management/forms/project_milestone.py b/app/project_management/forms/project_milestone.py new file mode 100644 index 000000000..41ee395bd --- /dev/null +++ b/app/project_management/forms/project_milestone.py @@ -0,0 +1,109 @@ +from django import forms +from django.urls import reverse +from django.db.models import Q + +from app import settings + +from core.forms.common import CommonModelForm +from core.templatetags.markdown import to_duration + +from project_management.models.project_milestone import ProjectMilestone + + + +class ProjectMilestoneForm(CommonModelForm): + + prefix = 'project' + + class Meta: + fields = [ + 'id', + 'organization', + 'name', + 'description', + 'project', + 'start_date', + 'finish_date', + ] + + + model = ProjectMilestone + + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.fields['start_date'].widget = forms.widgets.DateTimeInput(attrs={'type': 'datetime-local', 'format': "%Y-%m-%dT%H:%M"}) + self.fields['start_date'].input_formats = settings.DATETIME_FORMAT + self.fields['start_date'].format="%Y-%m-%dT%H:%M" + + self.fields['finish_date'].widget = forms.widgets.DateTimeInput(attrs={'type': 'datetime-local'}) + self.fields['finish_date'].input_formats = settings.DATETIME_FORMAT + self.fields['finish_date'].format="%Y-%m-%dT%H:%M" + + self.fields['description'].widget.attrs = {'style': "height: 800px; width: 1000px"} + + self.fields['project'].widget = self.fields['project'].hidden_widget() + + +class DetailForm(ProjectMilestoneForm): + + + tabs: dict = { + "details": { + "name": "Details", + "slug": "details", + "sections": [ + { + "layout": "double", + "left": [ + 'name', + 'percent_completed', + 'organization' + 'c_created', + 'c_modified', + ], + "right": [ + 'start_date', + 'finish_date', + ] + }, + { + "layout": "single", + "name": "Description", + "fields": [ + 'description', + ], + "markdown": [ + 'description', + ], + }, + ] + }, + "tasks": { + "name": "Tasks", + "slug": "tasks", + "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.url_index_view = reverse('Project Management:_project_view', kwargs={'pk': self.instance.project.id}) + '?tab=milestones' diff --git a/app/project_management/migrations/0001_initial.py b/app/project_management/migrations/0001_initial.py index 5eedf1007..848f40e18 100644 --- a/app/project_management/migrations/0001_initial.py +++ b/app/project_management/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.8 on 2024-09-13 05:06 +# Generated by Django 5.0.8 on 2024-09-14 06:29 import access.fields import access.models @@ -44,4 +44,25 @@ class Migration(migrations.Migration): 'ordering': ['code', 'name'], }, ), + migrations.CreateModel( + name='ProjectMilestone', + fields=[ + ('is_global', models.BooleanField(default=False)), + ('id', models.AutoField(primary_key=True, serialize=False, unique=True)), + ('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(max_length=50, unique=True)), + ('slug', access.fields.AutoSlugField()), + ('description', models.TextField(blank=True, default=None, null=True)), + ('start_date', models.DateTimeField(blank=True, help_text='When work commenced on the project.', null=True, verbose_name='Real Start Date')), + ('finish_date', models.DateTimeField(blank=True, help_text='When work was completed for the project', null=True, verbose_name='Real Finish Date')), + ('organization', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='access.organization', validators=[access.models.TenancyObject.validatate_organization_exists])), + ('project', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='project_management.project')), + ], + options={ + 'verbose_name': 'Project Milestone', + 'verbose_name_plural': 'Project Milestones', + 'ordering': ['name'], + }, + ), ] diff --git a/app/project_management/models/project_milestone.py b/app/project_management/models/project_milestone.py new file mode 100644 index 000000000..79fc5cd00 --- /dev/null +++ b/app/project_management/models/project_milestone.py @@ -0,0 +1,74 @@ +from django.contrib.auth.models import User +from django.db import models + +from .projects import Project, ProjectCommonFieldsName, SaveHistory + + + +class ProjectMilestone(ProjectCommonFieldsName): + + + class Meta: + + ordering = [ + 'name', + ] + + verbose_name = 'Project Milestone' + + verbose_name_plural = 'Project Milestones' + + + description = models.TextField( + blank = True, + default = None, + null= True, + ) + + start_date = models.DateTimeField( + blank = True, + help_text = 'When work commenced on the project.', + null = True, + verbose_name = 'Real Start Date', + ) + + finish_date = models.DateTimeField( + blank = True, + help_text = 'When work was completed for the project', + null = True, + verbose_name = 'Real Finish Date', + ) + + project = models.ForeignKey( + Project, + blank= False, + help_text = '', + on_delete=models.CASCADE, + null = False, + ) + + model_notes = None + + + + def __str__(self): + + return self.name + + + @property + def parent_object(self): + """ Fetch the parent object """ + + return self.project + + + @property + def percent_completed(self) -> str: # Auto-Calculate + """ How much of the milestone is completed. + + Returns: + str: Calculated percentage of project completion. + """ + + return 'xx %' diff --git a/app/project_management/templates/project_management/project.html.j2 b/app/project_management/templates/project_management/project.html.j2 index c380aba9c..29854730f 100644 --- a/app/project_management/templates/project_management/project.html.j2 +++ b/app/project_management/templates/project_management/project.html.j2 @@ -17,6 +17,39 @@ +
+ + {% include 'content/section.html.j2' with tab=form.tabs.milestones %} + + + + + + + + + + + {% if milestones %} + {% for milestone in milestones %} + + + + + + + {% endfor %} + {% else %} + + {% endif %} +
NameCompletedStartedFinished
+ + {{ milestone.name }} + + {{ milestone.percent_completed }}{{ milestone.start_date }}{{ milestone.finish_date }}
Nothing Found
+ +
+
diff --git a/app/project_management/templates/project_management/project_milestone.html.j2 b/app/project_management/templates/project_management/project_milestone.html.j2 new file mode 100644 index 000000000..221bc7e5e --- /dev/null +++ b/app/project_management/templates/project_management/project_milestone.html.j2 @@ -0,0 +1,79 @@ +{% extends 'detail.html.j2' %} + +{% block additional-stylesheet %} + {% load static %} + +{% endblock additional-stylesheet %} + +{% load json %} +{% load markdown %} + + +{% block tabs %} + +
+ + {% include 'content/section.html.j2' with tab=form.tabs.details %} + +
+ +
+ + {% include 'content/section.html.j2' with tab=form.tabs.tasks %} + + + + + + + + + + {% for task in tasks %} + + + + + + + + {% endfor %} +
IDTitleStatusTypeCreated
{{ task.id }} + {% if task.get_ticket_type_display|lower == 'change' %} + + {% elif task.get_ticket_type_display|lower == 'incident' %} + + {% elif task.get_ticket_type_display|lower == 'problem' %} + + {% elif task.get_ticket_type_display|lower == 'request' %} + + {% elif task.get_ticket_type_display|lower == 'project task' %} + + {% else %} + + {% endif %} + {{ task.title }} + + + {% include 'core/ticket/badge_ticket_status.html.j2' with ticket_status_text=task.get_status_display ticket_status=task.get_status_display|ticket_status %} + {{ task.get_ticket_type_display }}{{ task.created }}
+ +
+ +
+ + {% include 'content/section.html.j2' with tab=form.tabs.notes %} + + {{ notes_form }} + +
+ {% if notes %} + {% for note in notes%} + {% include 'note.html.j2' %} + {% endfor %} + {% endif %} +
+ +
+ +{% endblock %} diff --git a/app/project_management/urls.py b/app/project_management/urls.py index 3fb94dcf0..1288be39c 100644 --- a/app/project_management/urls.py +++ b/app/project_management/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from .views import project +from .views import project, project_milestones from core.views import ticket, ticket_comment @@ -14,6 +14,11 @@ path("project//edit", project.Change.as_view(), name="_project_change"), path("project//delete", project.Delete.as_view(), name="_project_delete"), + path('project//milestone/add', project_milestones.Add.as_view(), name="_project_milestone_add"), + path('project//milestone//edit', project_milestones.Change.as_view(), name="_project_milestone_change"), + path('project//milestone//delete', project_milestones.Delete.as_view(), name="_project_milestone_delete"), + path('project//milestone/', project_milestones.View.as_view(), name="_project_milestone_view"), + path('project///add', ticket.Add.as_view(), name="_project_task_add"), path('project////edit', ticket.Change.as_view(), name="_project_task_change"), path('project////delete', ticket.Delete.as_view(), name="_project_task_delete"), diff --git a/app/project_management/views/project.py b/app/project_management/views/project.py index 7b6f5f227..76432e9dc 100644 --- a/app/project_management/views/project.py +++ b/app/project_management/views/project.py @@ -13,8 +13,8 @@ from core.models.ticket.ticket import Ticket from core.views.common import AddView, ChangeView, DeleteView, DisplayView, IndexView -from project_management.forms.project import ProjectForm, DetailForm -from project_management.models.projects import Project +from project_management.forms.project import Project, ProjectForm, DetailForm +from project_management.models.project_milestone import ProjectMilestone from settings.models.user_settings import UserSettings @@ -170,6 +170,8 @@ def get_context_data(self, **kwargs): # context['notes_form'] = AddNoteForm(prefix='note') # context['notes'] = Notes.objects.filter(service=self.kwargs['pk']) + context['milestones'] = ProjectMilestone.objects.filter(project__id=self.kwargs['pk']) + context['project_tasks'] = Ticket.objects.filter( project = self.object, ) diff --git a/app/project_management/views/project_milestones.py b/app/project_management/views/project_milestones.py new file mode 100644 index 000000000..af37e6333 --- /dev/null +++ b/app/project_management/views/project_milestones.py @@ -0,0 +1,182 @@ +import json + +from django.contrib.auth.mixins import PermissionRequiredMixin +from django.core.paginator import Paginator +from django.db.models import Q +from django.urls import reverse +from django.views import generic + +from access.mixin import OrganizationPermission + +from core.forms.comment import AddNoteForm +from core.models.notes import Notes +from core.models.ticket.ticket import Ticket +from core.views.common import AddView, ChangeView, DeleteView, DisplayView, IndexView + +from project_management.forms.project_milestone import DetailForm, ProjectMilestone, ProjectMilestoneForm + +from settings.models.user_settings import UserSettings + + + +class Add(AddView): + + form_class = ProjectMilestoneForm + + model = ProjectMilestone + + permission_required = [ + 'project_management.add_projectmilestone', + ] + + template_name = 'form.html.j2' + + + def get_initial(self): + initial = super().get_initial() + + initial.update({ + 'project': self.kwargs['project_id'] + }) + + return initial + + def form_valid(self, form): + form.instance.is_global = False + return super().form_valid(form) + + + def get_success_url(self, **kwargs): + + return reverse('Project Management:_project_view', kwargs={'pk': self.kwargs['project_id']}) + '?tab=milestones' + + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context['content_title'] = 'Create a Project Milestone' + + return context + + + +class Change(ChangeView): + + form_class = ProjectMilestoneForm + + model = ProjectMilestone + + permission_required = [ + 'project_management.change_projectmilestone', + ] + + template_name = 'form.html.j2' + + + def form_valid(self, form): + form.instance.is_global = False + return super().form_valid(form) + + + def get_success_url(self, **kwargs): + + return reverse('Project Management:_project_view', kwargs={'pk': self.kwargs['pk']}) + + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context['content_title'] = 'Edit' + + return context + + + +class Delete(DeleteView): + + model = ProjectMilestone + + permission_required = [ + 'project_management.delete_projectmilestone', + ] + + template_name = 'form.html.j2' + + + def get_success_url(self, **kwargs): + + return reverse('Project Management:_project_view', kwargs={'pk': self.kwargs['project_id']}) + '?tab=milestones' + + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + context['content_title'] = 'Delete ' + self.object.name + + return context + + + +class View(ChangeView): + + model = ProjectMilestone + + permission_required = [ + 'project_management.view_projectmilestone' + ] + + template_name = 'project_management/project_milestone.html.j2' + + form_class = DetailForm + + context_object_name = "project" + + + def get_initial(self): + initial = super().get_initial() + + initial.update({ + 'project': self.kwargs['project_id'] + }) + + return initial + + + def get_context_data(self, **kwargs): + + context = super().get_context_data(**kwargs) + + context['tasks'] = Ticket.objects.filter( + project = self.object.project, + milestone = self.kwargs['pk'], + ) + + context['model_docs_path'] = self.model._meta.app_label + '/' + self.model._meta.model_name + '/' + + context['model_pk'] = self.kwargs['pk'] + context['model_name'] = self.model._meta.verbose_name.replace(' ', '') + + context['model_delete_url'] = reverse('Project Management:_project_milestone_delete', kwargs={'project_id': self.kwargs['project_id'], 'pk': self.kwargs['pk']}) + + context['content_title'] = context['project'].name + + return context + + + # def post(self, request, *args, **kwargs): + + # project = self.model.objects.get(pk=self.kwargs['pk']) + + # notes = AddNoteForm(request.POST, prefix='note') + + # if notes.is_bound and notes.is_valid() and notes.instance.note != '': + + # if request.user.has_perm('core.add_notes'): + + # notes.instance.organization = device.organization + # notes.instance.project = project + # notes.instance.usercreated = request.user + + # notes.save() + + # return super().post(request, *args, **kwargs)