feat(project_management): Add Project State to the UI

ref: #294 #295 #300
This commit is contained in:
2024-09-17 17:20:00 +09:30
parent 5ad974f947
commit f8e96a556d
9 changed files with 424 additions and 2 deletions

View File

@ -0,0 +1,87 @@
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_states import ProjectState
class ProjectStateForm(CommonModelForm):
class Meta:
fields = [
'id',
'organization',
'name',
'model_notes',
'runbook'
]
model = ProjectState
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)
# # self.fields['description'].widget.attrs = {'style': "height: 800px; width: 1000px"}
class DetailForm(ProjectStateForm):
tabs: dict = {
"details": {
"name": "Details",
"slug": "details",
"sections": [
{
"layout": "double",
"left": [
'name',
'runbook',
'organization',
'c_created',
'c_modified',
],
"right": [
'model_notes',
]
},
]
},
"notes": {
"name": "Notes",
"slug": "notes",
"sections": []
}
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['c_created'] = forms.DateTimeField(
label = 'Created',
input_formats=settings.DATETIME_FORMAT,
disabled = True,
initial = self.instance.created,
)
self.fields['c_modified'] = forms.DateTimeField(
label = 'Modified',
input_formats=settings.DATETIME_FORMAT,
disabled = True,
initial = self.instance.modified,
)
self.tabs['details'].update({
"edit_url": reverse('Settings:_project_state_change', args=(self.instance.pk,))
})
self.url_index_view = reverse('Settings:_project_states')

View File

@ -0,0 +1,19 @@
# Generated by Django 5.0.8 on 2024-09-17 05:43
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project_management', '0007_project_priority'),
]
operations = [
migrations.AlterField(
model_name='project',
name='project_type',
field=models.ForeignKey(blank=True, help_text='Type of project', null=True, on_delete=django.db.models.deletion.SET_NULL, to='project_management.projecttype', verbose_name='Project Type'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 5.0.8 on 2024-09-17 05:45
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('project_management', '0008_alter_project_project_type'),
]
operations = [
migrations.AlterField(
model_name='project',
name='state',
field=models.ForeignKey(blank=True, help_text='State of the project', null=True, on_delete=django.db.models.deletion.SET_NULL, to='project_management.projectstate', verbose_name='Project State'),
),
]

View File

@ -91,7 +91,7 @@ class Project(ProjectCommonFieldsName):
state = models.ForeignKey( state = models.ForeignKey(
ProjectState, ProjectState,
blank= False, blank= True,
help_text = 'State of the project', help_text = 'State of the project',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null = True, null = True,
@ -101,7 +101,7 @@ class Project(ProjectCommonFieldsName):
project_type = models.ForeignKey( project_type = models.ForeignKey(
ProjectType, ProjectType,
blank= False, blank= True,
help_text = 'Type of project', help_text = 'Type of project',
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
null = True, null = True,

View File

@ -0,0 +1,42 @@
{% 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 %}
</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

@ -0,0 +1,48 @@
{% extends 'base.html.j2' %}
{% block content_header_icon %}{% endblock %}
{% block content %}
<input type="button" value="New Project State" onclick="window.location='{% url 'Settings:_project_state_add' %}';">
<table class="data">
<tr>
<th>Name</th>
<th>Organization</th>
<th>Created</th>
<th>Modified</th>
<th>&nbsp;</th>
</tr>
{% for project_state in project_states %}
<tr>
<td>
<a href="{% url 'Settings:_project_state_view' pk=project_state.id %}">{{ project_state.name }}</a>
</td>
<td>{% if project_state.is_global %}Global{% else %}{{ project_state.organization }}{% endif %}</td>
<td>{{ project_state.created }}</td>
<td>{{ project_state.modified }}</td>
<td>&nbsp;</td>
</tr>
{% endfor %}
</table>
<div class="pagination">
<span class="step-links">
{% if page_obj.has_previous %}
<a href="?page=1">&laquo; first</a>
<a href="?page={{ page_obj.previous_page_number }}">previous</a>
{% endif %}
<span class="current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">next</a>
<a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
{% endif %}
</span>
</div>
{% endblock %}

View File

@ -0,0 +1,192 @@
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_state import DetailForm, ProjectState, ProjectStateForm
from settings.models.user_settings import UserSettings
class Add(AddView):
form_class = ProjectStateForm
model = ProjectState
permission_required = [
'project_management.add_projectstate',
]
def get_initial(self):
initial: dict = {
'organization': UserSettings.objects.get(user = self.request.user).default_organization
}
return initial
def get_success_url(self, **kwargs):
return reverse('Settings:_project_states')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['content_title'] = 'New Group'
return context
class Change(ChangeView):
context_object_name = "project_task"
form_class = ProjectStateForm
model = ProjectState
permission_required = [
'project_management.change_projectstate',
]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['content_title'] = self.object.title
return context
def get_success_url(self, **kwargs):
return reverse('Settings:_project_state_view', args=(self.kwargs['pk'],))
class Delete(DeleteView):
model = ProjectState
permission_required = [
'project_management.delete_projectstate',
]
template_name = 'form.html.j2'
def get_success_url(self, **kwargs):
return reverse('Settings:_project_states')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['content_title'] = 'Delete ' + self.object.name
return context
class Index(IndexView):
model = ProjectState
permission_required = [
'project_management.view_projectstate',
]
template_name = 'project_management/project_state_index.html.j2'
context_object_name = "project_states"
paginate_by = 10
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['content_title'] = 'Project States'
return context
def get_queryset(self):
if self.request.user.is_superuser:
return self.model.objects.filter().order_by('name')
else:
return self.model.objects.filter(Q(organization__in=self.user_organizations()) | Q(is_global = True)).order_by('name')
class View(ChangeView):
model = ProjectState
permission_required = [
'project_management.view_projectstate'
]
template_name = 'project_management/project_state.html.j2'
form_class = DetailForm
context_object_name = "project"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['model_docs_path'] = self.model._meta.app_label + '/' + self.model._meta.model_name + '/'
context['model_pk'] = self.kwargs['pk']
context['model_name'] = self.model._meta.verbose_name.replace(' ', '')
context['model_delete_url'] = reverse('Settings:_project_state_delete', kwargs={'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)

View File

@ -75,6 +75,13 @@ div#content article h3 {
</ul> </ul>
</article> </article>
<article style="">
<h3>Project Management</h3>
<ul>
<li><a href="{% url 'Settings:_project_states' %}">Project States</a></li>
</ul>
</article>
</div> </div>

View File

@ -10,6 +10,8 @@ from itam.views import device_type, device_model, software_category
from itim.views import cluster_types, ports from itim.views import cluster_types, ports
from project_management.views import project_states
app_name = "Settings" app_name = "Settings"
urlpatterns = [ urlpatterns = [
@ -59,6 +61,12 @@ urlpatterns = [
path("port/<int:pk>/delete", ports.Delete.as_view(), name="_port_delete"), path("port/<int:pk>/delete", ports.Delete.as_view(), name="_port_delete"),
path("port/<int:pk>", ports.View.as_view(), name="_port_view"), path("port/<int:pk>", ports.View.as_view(), name="_port_view"),
path("project_states", project_states.Index.as_view(), name="_project_states"),
path("project_state/<int:pk>", project_states.View.as_view(), name="_project_state_view"),
path("project_state/add", project_states.Add.as_view(), name="_project_state_add"),
path("project_state/<int:pk>/edit", project_states.Change.as_view(), name="_project_state_change"),
path("project_state/<int:pk>/delete", project_states.Delete.as_view(), name="_project_state_delete"),
path("software_category", software_categories.Index.as_view(), name="_software_categories"), path("software_category", software_categories.Index.as_view(), name="_software_categories"),
path("software_category/<int:pk>", software_category.View.as_view(), name="_software_category_view"), path("software_category/<int:pk>", software_category.View.as_view(), name="_software_category_view"),
path("software_category/add/", software_category.Add.as_view(), name="_software_category_add"), path("software_category/add/", software_category.Add.as_view(), name="_software_category_add"),