feat(project_management): Add project milestones

ref: #285 #292
This commit is contained in:
2024-09-14 16:06:20 +09:30
parent a373247cda
commit 5e235617e0
16 changed files with 606 additions and 14 deletions

View File

@ -52,6 +52,18 @@ class TicketForm(
self.fields['ticket_type'].widget = self.fields['ticket_type'].hidden_widget() self.fields['ticket_type'].widget = self.fields['ticket_type'].hidden_widget()
self.fields['organization'].widget = self.fields['organization'].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() original_fields = self.fields.copy()
ticket_type = [] ticket_type = []

View File

@ -49,6 +49,7 @@ class TicketValidation(
'planned_finish_date', 'planned_finish_date',
'priority', 'priority',
'project', 'project',
'milestone',
'real_start_date', 'real_start_date',
'real_finish_date', 'real_finish_date',
'subscribed_users', 'subscribed_users',
@ -67,6 +68,7 @@ class TicketValidation(
'planned_finish_date', 'planned_finish_date',
'priority', 'priority',
'project', 'project',
'milestone',
'real_start_date', 'real_start_date',
'real_finish_date', 'real_finish_date',
'subscribed_users', 'subscribed_users',

View File

@ -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.fields
import access.models import access.models
import core.lib.slash_commands
import core.models.ticket.ticket import core.models.ticket.ticket
import core.models.ticket.ticket_comment import core.models.ticket.ticket_comment
import django.db.models.deletion import django.db.models.deletion
@ -66,6 +67,7 @@ class Migration(migrations.Migration):
'verbose_name_plural': 'Comments', 'verbose_name_plural': 'Comments',
'ordering': ['ticket', 'parent_id'], 'ordering': ['ticket', 'parent_id'],
}, },
bases=(core.lib.slash_commands.SlashCommands, models.Model),
), ),
migrations.CreateModel( migrations.CreateModel(
name='TicketCommentCategory', 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')), ('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_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)')), ('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={ options={
'verbose_name': 'Ticket', 'verbose_name': 'Ticket',
@ -130,5 +130,6 @@ class Migration(migrations.Migration):
'ordering': ['id'], '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')], '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),
), ),
] ]

View File

@ -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 access.models
import core.models.ticket.ticket_comment import core.models.ticket.ticket_comment
@ -18,6 +18,21 @@ class Migration(migrations.Migration):
] ]
operations = [ 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( migrations.AddField(
model_name='ticket', model_name='ticket',
name='project', name='project',

View File

@ -11,7 +11,7 @@ from core.lib.slash_commands import SlashCommands
from core.middleware.get_request import get_request from core.middleware.get_request import get_request
from core.models.ticket.ticket_category import TicketCategory 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 @@ class Ticket(
verbose_name = 'Project', 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( opened_by = models.ForeignKey(
User, User,
@ -642,6 +651,7 @@ class Ticket(
'category' 'category'
'urgency', 'urgency',
'project', 'project',
'milestone',
'priority', 'priority',
'impact', 'impact',
'subscribed_teams', 'subscribed_teams',
@ -680,6 +690,7 @@ class Ticket(
fields_project_task: list(str()) = common_fields + [ fields_project_task: list(str()) = common_fields + [
'category', 'category',
'milestone',
'status', 'status',
'urgency', 'urgency',
'priority', 'priority',
@ -695,6 +706,7 @@ class Ticket(
tech_fields = [ tech_fields = [
'category', 'category',
'project', 'project',
'milestone',
'assigned_users', 'assigned_users',
'assigned_teams', 'assigned_teams',
'subscribed_teams', 'subscribed_teams',

View File

@ -132,16 +132,22 @@
</span> </span>
</fieldset> </fieldset>
{% endif %} {% endif %}
{% if ticket.project %}
<fieldset> <fieldset>
<label>Project</label> <label>Project</label>
<span class="text"> <span class="text">
{% if ticket.project %}
<a href="{% url 'Project Management:_project_view' pk=ticket.project_id %}">{{ ticket.project }}</a> <a href="{% url 'Project Management:_project_view' pk=ticket.project_id %}">{{ ticket.project }}</a>
{% else %}
-
{% endif %}
</span> </span>
</fieldset> </fieldset>
{% endif %}
{% if ticket.milestone %}
<fieldset>
<label>Milestone</label>
<span class="text">
<a href="{% url 'Project Management:_project_milestone_view' project_id=ticket.project_id pk=ticket.milestone.id %}">{{ ticket.milestone }}</a>
</span>
</fieldset>
{% endif %}
<fieldset> <fieldset>
<label>Priority</label> <label>Priority</label>
<span class="text">U{{ ticket.get_urgency_display }} / I{{ ticket.get_impact_display }} / P{{ ticket.get_priority_display }}</span> <span class="text">U{{ ticket.get_urgency_display }} / I{{ ticket.get_impact_display }} / P{{ ticket.get_priority_display }}</span>

View File

@ -54,4 +54,38 @@ class TicketModel(
as a subscribed user. as a subscribed user.
""" """
pass 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

View File

@ -97,6 +97,11 @@ class DetailForm(ProjectForm):
"slug": "tasks", "slug": "tasks",
"sections": [] "sections": []
}, },
"milestones": {
"name": "Milestones",
"slug": "milestones",
"sections": []
},
"notes": { "notes": {
"name": "Notes", "name": "Notes",
"slug": "notes", "slug": "notes",

View File

@ -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'

View File

@ -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.fields
import access.models import access.models
@ -44,4 +44,25 @@ class Migration(migrations.Migration):
'ordering': ['code', 'name'], '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'],
},
),
] ]

View File

@ -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 %'

View File

@ -17,6 +17,39 @@
</div> </div>
<div id="milestones" class="content-tab">
{% include 'content/section.html.j2' with tab=form.tabs.milestones %}
<input type="button" value="New Milestone" onclick="window.location='{% url 'Project Management:_project_milestone_add' project_id=project.id %}';">
<table>
<thead>
<th>Name</th>
<th>Completed</th>
<th>Started</th>
<th>Finished</th>
</thead>
{% if milestones %}
{% for milestone in milestones %}
<tr>
<td>
<a href="{% url 'Project Management:_project_milestone_view' project_id=project.id pk=milestone.id %}">
{{ milestone.name }}
</a>
</td>
<td>{{ milestone.percent_completed }}</td>
<td>{{ milestone.start_date }}</td>
<td>{{ milestone.finish_date }}</td>
</tr>
{% endfor %}
{% else %}
<tr><td colspan="4">Nothing Found</td></tr>
{% endif %}
</table>
</div>
<div id="tasks" class="content-tab"> <div id="tasks" class="content-tab">

View File

@ -0,0 +1,79 @@
{% extends 'detail.html.j2' %}
{% block additional-stylesheet %}
{% load static %}
<link rel="stylesheet" href="{% static 'ticketing.css' %}">
{% endblock additional-stylesheet %}
{% load json %}
{% load markdown %}
{% block tabs %}
<div id="details" class="content-tab">
{% include 'content/section.html.j2' with tab=form.tabs.details %}
</div>
<div id="tasks" class="content-tab">
{% include 'content/section.html.j2' with tab=form.tabs.tasks %}
<table>
<tr>
<th>ID</th>
<th>Title</th>
<th>Status</th>
<th>Type</th>
<th>Created</th>
</tr>
{% for task in tasks %}
<tr>
<td>{{ task.id }}</td>
<td>
{% if task.get_ticket_type_display|lower == 'change' %}
<a href="{% url 'ITIM:_ticket_change_view' ticket_type='change' pk=task.id %}">
{% elif task.get_ticket_type_display|lower == 'incident' %}
<a href="{% url 'ITIM:_ticket_incident_view' ticket_type='incident' pk=task.id %}">
{% elif task.get_ticket_type_display|lower == 'problem' %}
<a href="{% url 'ITIM:_ticket_problem_view' ticket_type='problem' pk=task.id %}">
{% elif task.get_ticket_type_display|lower == 'request' %}
<a href="{% url 'Assistance:_ticket_request_view' ticket_type='request' pk=task.id %}">
{% elif task.get_ticket_type_display|lower == 'project task' %}
<a href="{% url 'Project Management:_project_task_view' ticket_type='project_task' project_id=project.id pk=task.id %}">
{% else %}
<a href=""></a>
{% endif %}
{{ task.title }}
</a>
</td>
<td>
{% include 'core/ticket/badge_ticket_status.html.j2' with ticket_status_text=task.get_status_display ticket_status=task.get_status_display|ticket_status %}
</td>
<td>{{ task.get_ticket_type_display }}</td>
<td>{{ task.created }}</td>
</tr>
{% endfor %}
</table>
</div>
<div id="notes" class="content-tab">
{% include 'content/section.html.j2' with tab=form.tabs.notes %}
{{ notes_form }}
<input type="submit" name="{{notes_form.prefix}}" value="Submit" />
<div class="comments">
{% if notes %}
{% for note in notes%}
{% include 'note.html.j2' %}
{% endfor %}
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -1,6 +1,6 @@
from django.urls import path from django.urls import path
from .views import project from .views import project, project_milestones
from core.views import ticket, ticket_comment from core.views import ticket, ticket_comment
@ -14,6 +14,11 @@ urlpatterns = [
path("project/<int:pk>/edit", project.Change.as_view(), name="_project_change"), path("project/<int:pk>/edit", project.Change.as_view(), name="_project_change"),
path("project/<int:pk>/delete", project.Delete.as_view(), name="_project_delete"), path("project/<int:pk>/delete", project.Delete.as_view(), name="_project_delete"),
path('project/<int:project_id>/milestone/add', project_milestones.Add.as_view(), name="_project_milestone_add"),
path('project/<int:project_id>/milestone/<int:pk>/edit', project_milestones.Change.as_view(), name="_project_milestone_change"),
path('project/<int:project_id>/milestone/<int:pk>/delete', project_milestones.Delete.as_view(), name="_project_milestone_delete"),
path('project/<int:project_id>/milestone/<int:pk>', project_milestones.View.as_view(), name="_project_milestone_view"),
path('project/<int:project_id>/<str:ticket_type>/add', ticket.Add.as_view(), name="_project_task_add"), path('project/<int:project_id>/<str:ticket_type>/add', ticket.Add.as_view(), name="_project_task_add"),
path('project/<int:project_id>/<str:ticket_type>/<int:pk>/edit', ticket.Change.as_view(), name="_project_task_change"), path('project/<int:project_id>/<str:ticket_type>/<int:pk>/edit', ticket.Change.as_view(), name="_project_task_change"),
path('project/<int:project_id>/<str:ticket_type>/<int:pk>/delete', ticket.Delete.as_view(), name="_project_task_delete"), path('project/<int:project_id>/<str:ticket_type>/<int:pk>/delete', ticket.Delete.as_view(), name="_project_task_delete"),

View File

@ -13,8 +13,8 @@ from core.models.notes import Notes
from core.models.ticket.ticket import Ticket from core.models.ticket.ticket import Ticket
from core.views.common import AddView, ChangeView, DeleteView, DisplayView, IndexView from core.views.common import AddView, ChangeView, DeleteView, DisplayView, IndexView
from project_management.forms.project import ProjectForm, DetailForm from project_management.forms.project import Project, ProjectForm, DetailForm
from project_management.models.projects import Project from project_management.models.project_milestone import ProjectMilestone
from settings.models.user_settings import UserSettings from settings.models.user_settings import UserSettings
@ -170,6 +170,8 @@ class View(ChangeView):
# context['notes_form'] = AddNoteForm(prefix='note') # context['notes_form'] = AddNoteForm(prefix='note')
# context['notes'] = Notes.objects.filter(service=self.kwargs['pk']) # 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( context['project_tasks'] = Ticket.objects.filter(
project = self.object, project = self.object,
) )

View File

@ -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)