From e40c9e3d8f6c41e74251b25492a238edb841f298 Mon Sep 17 00:00:00 2001 From: = Date: Sun, 8 Sep 2024 08:51:55 -0400 Subject: [PATCH] first commit --- Read.me | 67 +++++++++ app.js | 35 +++++ main/__init__.py | 0 main/admin.py | 3 + main/api_views.py | 12 ++ main/apps.py | 8 ++ main/forms.py | 12 ++ main/migrations/__init__.py | 0 main/models.py | 48 +++++++ main/serializers.py | 12 ++ main/signals.py | 10 ++ main/tests.py | 3 + main/urls.py | 19 +++ main/views.py | 64 +++++++++ manage.py | 22 +++ project_management/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 0 -> 159 bytes .../__pycache__/settings.cpython-311.pyc | Bin 0 -> 2593 bytes .../__pycache__/urls.cpython-311.pyc | Bin 0 -> 1030 bytes .../__pycache__/wsgi.cpython-311.pyc | Bin 0 -> 685 bytes project_management/asgi.py | 16 +++ project_management/settings.py | 128 ++++++++++++++++++ project_management/urls.py | 8 ++ project_management/wsgi.py | 16 +++ templates/create_project.html | 6 + templates/create_task.html | 6 + templates/load_project.html | 11 ++ templates/login.html | 9 ++ templates/project_dashboard.html | 12 ++ templates/welcome.html | 9 ++ 30 files changed, 536 insertions(+) create mode 100644 Read.me create mode 100644 app.js create mode 100644 main/__init__.py create mode 100644 main/admin.py create mode 100644 main/api_views.py create mode 100644 main/apps.py create mode 100644 main/forms.py create mode 100644 main/migrations/__init__.py create mode 100644 main/models.py create mode 100644 main/serializers.py create mode 100644 main/signals.py create mode 100644 main/tests.py create mode 100644 main/urls.py create mode 100644 main/views.py create mode 100644 manage.py create mode 100644 project_management/__init__.py create mode 100644 project_management/__pycache__/__init__.cpython-311.pyc create mode 100644 project_management/__pycache__/settings.cpython-311.pyc create mode 100644 project_management/__pycache__/urls.cpython-311.pyc create mode 100644 project_management/__pycache__/wsgi.cpython-311.pyc create mode 100644 project_management/asgi.py create mode 100644 project_management/settings.py create mode 100644 project_management/urls.py create mode 100644 project_management/wsgi.py create mode 100644 templates/create_project.html create mode 100644 templates/create_task.html create mode 100644 templates/load_project.html create mode 100644 templates/login.html create mode 100644 templates/project_dashboard.html create mode 100644 templates/welcome.html diff --git a/Read.me b/Read.me new file mode 100644 index 0000000..ea62c9b --- /dev/null +++ b/Read.me @@ -0,0 +1,67 @@ +Read.me +Provide code for following program + +Program runs on 5 containers using podman-dockerise + +First container is latest postgresql with pgadmin +Second container is latest couchdb +Third container is latest django +Fourth container is latest nodejs with latest react and latest redux +Fifth container is latest nginx which contains the landing page and serves static content + +All dynamic data is stored in an external nfs server defined in a configuration file for main django application. +Initial data that containers need is stored locally. + +Program starts by welcoming user and displaying login or register page served by nginx. +Registration is by email which by default is noone@nowhere.net +Program will use a certificate system to secure communication between containers where root certificate passkey is the secret in settings page of the main django application + +Django main application and apis it serves: +Djano main application is the user directory, project directory, and RBAC definitions directory. +First time running the django application default user is admin and password is "GoodMorning123!" which has full administrative rights. +First time admin login to the django application default landing page is the program configuration page which has following required fields to complete: + -Company Name (only one company can exist but subsidieries can be created - 256 characters maximum) + -First subsidiary name (organization unit responsible for administring the program - 256 characters maximum) + -Three letter for initial subsidiary + -Contact email address for issues (must have proper email form) which will be the email that will receive the login certificate for admin user + -email address for registration (which replaces the default noone@nowhere.net - must have proper email form) + -Address NFS server (test button to make sure program can create a test folder, create a file in test folder, delete test file, and delete test folder. Test must be done with success at least once for submit button to become usuable - until that it should be grayed out) +submit button will submit the form. Once form is submitted following will happen: + -Login for django environment will become certificate based + -Will replace default registration email with one submitted in the nginx landing page + -Will create initial database in postgresql for the subsidiary with three letter provided + -Will set admin with certificate as default postgresql database administrator (this certificate will be used to exchange data with other containers) + -Will create initial couchdb database for subsidieary and default project + -Will set admin with certificate as couchdb default administrator (this certificate will be used to exhange data with other containers) + -Initial landing page will switch to project dashboard displaying default project which will have project number made of three letters submitted and 0000001. For example if ABC is the three letter submitted project number will be ABC-0000001. More details on initial projects later. + -Will apply initial project template to the django database (more on project templates later) +What users can see on the landing page will be based on their role but admin user can see and modify everything. +Clicking on the project will open a new web page served by nginx but uses nodejs container. This is project landing page. What can be done in project will be done through this react application which will use couchdb to manage its data. + +Here is what project page will have: +It is a progressive web application page. It contains all the work packages. How work packages relate to each other is defined through template. +Web application uses graphql to get all the data it needs from postgresql and couchdb. +By default it contains all the work packages that come from default template. Template uses hashtaged object pairs with relation between them in curly braket. +Default template contains following lines at the start for subsidiary defined with letters ABC: +Vertices: +#WP0000001 {childof} #ABC0000001 +#T0000001 {childof} #WP0000001 +#T0000002 {childof} #T0000001 +#T0000002 {end-before-start} #T0000001 +#T0000001-TaskName {field-of} #T0000001 +#WP0000001-WorkPackageName {field-of} #WP0000001 +List of relations: +{childof} +{depends-on} +{end-before-start} +{end-after-start} +{field-of} + +CouchDB will store different fields which can accept pre-defined type of data for each hashtaged object. When defining a hashtag object user will be asked to provide type of data it should be. By default character. Provide pull down list of any type of data Couchdb can store that can be captured through the intake form. +All default initial hashtaged objects are CHAR type. +List of Vertices and list of relations are stored in CouchDB database for the project. List of relations is editable. Nodes for vertices are created through work package manager react application. +work package manager also is responsible for creating vertices. But nodes and relation they will use between two nodes must exist before use. That is vertice builder need to rely on existing searchable data. + +Hashtaged objects need to be unique and they are not case sensitive. + +Another react application shows the graph view based on vertices one or more relations (use filter to apply which relations to use to graph). Do not show unlinked nodes. \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..fbce924 --- /dev/null +++ b/app.js @@ -0,0 +1,35 @@ +import React, { useEffect, useState } from 'react'; + +function App() { + const [projects, setProjects] = useState([]); + const [tasks, setTasks] = useState([]); + + useEffect(() => { + fetch('/api/projects/') + .then(response => response.json()) + .then(data => setProjects(data)); + + fetch('/api/tasks/') + .then(response => response.json()) + .then(data => setTasks(data)); + }, []); + + return ( +
+

Projects

+ +

Tasks

+ +
+ ); +} + +export default App; \ No newline at end of file diff --git a/main/__init__.py b/main/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main/admin.py b/main/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/main/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/main/api_views.py b/main/api_views.py new file mode 100644 index 0000000..f3ff637 --- /dev/null +++ b/main/api_views.py @@ -0,0 +1,12 @@ +# main/api_views.py +from rest_framework import viewsets +from .models import Project, Task +from .serializers import ProjectSerializer, TaskSerializer + +class ProjectViewSet(viewsets.ModelViewSet): + queryset = Project.objects.all() + serializer_class = ProjectSerializer + +class TaskViewSet(viewsets.ModelViewSet): + queryset = Task.objects.all() + serializer_class = TaskSerializer diff --git a/main/apps.py b/main/apps.py new file mode 100644 index 0000000..5b1947b --- /dev/null +++ b/main/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + +class MainConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'main' + + def ready(self): + import main.signals diff --git a/main/forms.py b/main/forms.py new file mode 100644 index 0000000..9af62ea --- /dev/null +++ b/main/forms.py @@ -0,0 +1,12 @@ +from django import forms +from .models import Project, Task + +class ProjectForm(forms.ModelForm): + class Meta: + model = Project + fields = ['name', 'description'] + +class TaskForm(forms.ModelForm): + class Meta: + model = Task + fields = ['name', 'description', 'status'] \ No newline at end of file diff --git a/main/migrations/__init__.py b/main/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/main/models.py b/main/models.py new file mode 100644 index 0000000..41249c4 --- /dev/null +++ b/main/models.py @@ -0,0 +1,48 @@ +from django.db import models + +# Create your models here. +from django.contrib.auth.models import User +from django.db import models +from django.conf import settings + +class Profile(models.Model): + ROLE_CHOICES = [ + ('Application Admin', 'Application Admin'), + ('Application Designer', 'Application Designer'), + ('Database Admin', 'Database Admin'), + ('PMO Admin', 'PMO Admin'), + ('Program Manager', 'Program Manager'), + ('Project Manager', 'Project Manager'), + ('Project Coordinator', 'Project Coordinator'), + ('Project User', 'Project User'), + ('Project Initiator', 'Project Initiator'), + ] + + user = models.OneToOneField(User, on_delete=models.CASCADE) + role = models.CharField(max_length=50, choices=ROLE_CHOICES) + + def __str__(self): + return f"{self.user.username} - {self.role}" + + +class Project(models.Model): + name = models.CharField(max_length=255) + description = models.TextField() + initiator = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='initiated_projects') + manager = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='managed_projects') + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.name + +class Task(models.Model): + project = models.ForeignKey(Project, on_delete=models.CASCADE, related_name='tasks') + name = models.CharField(max_length=255) + description = models.TextField(blank=True) + status = models.CharField(max_length=50, choices=[('todo', 'To Do'), ('in_progress', 'In Progress'), ('done', 'Done')], default='todo') + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + def __str__(self): + return self.name \ No newline at end of file diff --git a/main/serializers.py b/main/serializers.py new file mode 100644 index 0000000..17c24a1 --- /dev/null +++ b/main/serializers.py @@ -0,0 +1,12 @@ +from rest_framework import serializers +from .models import Project, Task + +class ProjectSerializer(serializers.ModelSerializer): + class Meta: + model = Project + fields = '__all__' + +class TaskSerializer(serializers.ModelSerializer): + class Meta: + model = Task + fields = '__all__' \ No newline at end of file diff --git a/main/signals.py b/main/signals.py new file mode 100644 index 0000000..ac06aea --- /dev/null +++ b/main/signals.py @@ -0,0 +1,10 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver +from django.contrib.auth.models import User +from .models import Profile + +@receiver(post_save, sender=User) +def create_or_update_user_profile(sender, instance, created, **kwargs): + if created: + Profile.objects.create(user=instance) + instance.profile.save() \ No newline at end of file diff --git a/main/tests.py b/main/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/main/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/main/urls.py b/main/urls.py new file mode 100644 index 0000000..4ae88de --- /dev/null +++ b/main/urls.py @@ -0,0 +1,19 @@ +# main/urls.py +from .views import create_project, project_dashboard, create_task +from django.urls import path, include +from rest_framework.routers import DefaultRouter +from .api_views import ProjectViewSet, TaskViewSet + +router = DefaultRouter() +router.register(r'projects', ProjectViewSet) +router.register(r'tasks', TaskViewSet) + +urlpatterns = [ + path('api/', include(router.urls)), + path('login/', login_view, name='login'), + path('logout/', logout_view, name='logout'), + path('welcome/', welcome_view, name='welcome'), + path('projects/create/', create_project, name='create_project'), + path('projects//', project_dashboard, name='project_dashboard'), + path('projects//tasks/create/', create_task, name='create_task'), +] diff --git a/main/views.py b/main/views.py new file mode 100644 index 0000000..60a156b --- /dev/null +++ b/main/views.py @@ -0,0 +1,64 @@ +# main/views.py +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth.decorators import login_required +from .models import Project, Task +from .forms import ProjectForm, TaskForm + +@login_required +def create_project(request): + if request.method == 'POST': + form = ProjectForm(request.POST) + if form.is_valid(): + project = form.save(commit=False) + project.initiator = request.user + project.save() + # Notify PMO Admins + pmo_admins = User.objects.filter(profile__role='PMO Admin') + for admin in pmo_admins: + send_mail( + 'New Project Created', + f'A new project "{project.name}" has been created and requires a project manager assignment.', + 'admin@example.com', + [admin.email], + fail_silently=False, + ) + return redirect('project_dashboard', project_id=project.id) + else: + form = ProjectForm() + return render(request, 'create_project.html', {'form': form}) + +# main/views.py +@login_required +def load_project(request): + if request.method == 'POST': + project_id = request.POST['project_id'] + return redirect('project_dashboard', project_id=project_id) + projects = Project.objects.all() + return render(request, 'load_project.html', {'projects': projects}) + + +@login_required +def project_dashboard(request, project_id): + project = get_object_or_404(Project, id=project_id) + tasks = project.tasks.all() + return render(request, 'project_dashboard.html', {'project': project, 'tasks': tasks}) + +@login_required +def create_task(request, project_id): + project = get_object_or_404(Project, id=project_id) + if request.method == 'POST': + form = TaskForm(request.POST) + if form.is_valid(): + task = form.save(commit=False) + task.project = project + task.save() + return redirect('project_dashboard', project_id=project.id) + else: + form = TaskForm() + return render(request, 'create_task.html', {'form': form, 'project': project}) + +# main/views.py +from django.core.mail import send_mail +from django.contrib.auth.models import User + + diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..dff3903 --- /dev/null +++ b/manage.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + + +def main(): + """Run administrative tasks.""" + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'project_management.settings') + try: + from django.core.management import execute_from_command_line + except ImportError as exc: + raise ImportError( + "Couldn't import Django. Are you sure it's installed and " + "available on your PYTHONPATH environment variable? Did you " + "forget to activate a virtual environment?" + ) from exc + execute_from_command_line(sys.argv) + + +if __name__ == '__main__': + main() diff --git a/project_management/__init__.py b/project_management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/project_management/__pycache__/__init__.cpython-311.pyc b/project_management/__pycache__/__init__.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8a822c3c1782ac816a02a304a87e34a069ffb85e GIT binary patch literal 159 zcmZ3^%ge<81i=$mB!lS3AOZ#$p^VRLK*n^26oz01O-8?!3`I;p{%4TnFC#arn1G`E ztkmR^;+Oyf0|O(2nDV6Jg8ZVA#2h46e0*kJW=VX!UP0wA4x8Nkl+v73yCPPg5g<#7 U`GLd-W=2NF4-7D(h#4pb0G=Hr!T*d2&j=u1}cg6YN{ zeFgWe79`x3RoXBm-5hFOp}Pj)&_mc|<_5D2)@y*;)^tM4DmZ9MrmU$n40Brsm$E`W zh6qSx+cZ1Ioop6sO@kTu7)8+3+F7Dz0WfQ79l2>_Mf|M=3$)|jJz+!M`Ewyz?Y+t%tZ3$kjEW>+T*=Z)+48v4DZ z?$w>{N0+drcjxcj>MXbScD|lp+S+T}Ubv6%?_K{4KVQ`F%~~d4r3WxfQ&UY{ZZQ3% zT4yF#yWsJ{#G-3LRMXt+5W||85E{fVAk8NQZ4gK;u$Y^)FyUaD?r|8IptUfRwq=YJ zvMcF?>F-lE_ZZ&YFfbcOGHZDfZ0MdDv*!nEV^*1yPT-{5n#$z*um4ar^erX_l9P%bN`-M1WIJt>Ys>Y2s2I(8Ky7it-D01`3j?{bt&A@!Qrb}&7 z?zmNNjM*iQ8jb1dieY7kI3{U#6zDak0RTkBqh4V0{kE$O#fF{g7r=8#fbNU`+H`mtEgz`}y-(xV6WqfvUmCZfm zN_pFtFI0Iu#8xU|p9_{NwL-bXTXV-L+cjD;BXv5E>Haa=K}@!!u3{e5HEtYd(0HoI zCRwr~usGy19y`F5)(a)h_LbNTPKRq1Sq~;c@7GR8GK^hK$0FPyGVZ%$>ITtS({0(V zNqD&}E0Wt1j|Li=ZmpaaX+u`!cDG$5YRlX{w*UOJ_JdZb3}ZY7xPMxJQnyWXxp@-7 zPt=93VSDSfoE3z79$F@AeK>@LOtv*l6oXlnThLP4noJa2%FNh7QG_d86zyOKE=omi z=w3*z9TG+Na&gynVra@UVh1{Kuc)RST4j08J!?C{bGa&46CZM4+5SAYT3@&0Y_VA0 z{zv2u8H+(F;_0F+2IJiEpN}V_1Xim!t(rPxtbRrvBd%i%TR*xB3oLov+JCgE9W@? zENpP1P==BT)gj=*joT$V;BqwqBCi2*&h>7m^4uC*FV;jrE{kggu9*KFy>&ky?{2Q$ zd0ZKMSB=M&#l^*&i;s`0dW;)=-r&{1Qf&Zp8qhJ z>cyQ%Fy(R3i1ZRpDwKeQ5drNqO47ZU6GCZ@I!LAt(zAy#+Clz{i8100rXx-`<_|c? zCEi5b(+fK^N-yrCBslgb4pPiPa`vaYuaj3Czc&erF7f*NigPGDwVxCmuLq1n{Yhnf z^2PY1I+&b|f8c@rJD1V)Y%hKoo9=}Vqf@=mA#5(-#86@ef{&ncpX{U5VCa`1o&N&o CYg?EA literal 0 HcmV?d00001 diff --git a/project_management/__pycache__/urls.cpython-311.pyc b/project_management/__pycache__/urls.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2440023eb274de1cef6a8fd37c02c33da3d13da2 GIT binary patch literal 1030 zcma)5O>fgM7-F0z$(~Zl8wpti5eJ?b?+ZWyJ?inXq=A0grc627Qtaw{_GaUV4woWV{+NLhexHA z;li%ParnmYie#C)QJ8XRw!=h>12C|v)G-SMZpZW7U*;ZqR^B9($0-Dkar{lImdP06+J4aV4-e~&S~GaPUw!k+KV7@4-&IWONc2>>m=%M-hzNqr zk}7*Rlj4S^0yhUvZ9|n6nHWZ?6q#cZDeIDBuCtW|&hK!YYr1jwcH0NDw^i+fN~Q9& z(!OI|^bE9{oZQ@dHhbOYY00FUp9AWhnWi>EXlfNvVG8-Q2FN+PpSKBYkN@uzus%3H nVf_cJk2lCreate New Project +
+ {% csrf_token %} + {{ form.as_p }} + +
\ No newline at end of file diff --git a/templates/create_task.html b/templates/create_task.html new file mode 100644 index 0000000..e407351 --- /dev/null +++ b/templates/create_task.html @@ -0,0 +1,6 @@ +

Create Task for Project: {{ project.name }}

+
+ {% csrf_token %} + {{ form.as_p }} + +
diff --git a/templates/load_project.html b/templates/load_project.html new file mode 100644 index 0000000..cfeacf0 --- /dev/null +++ b/templates/load_project.html @@ -0,0 +1,11 @@ + +

Load Existing Project

+
+ {% csrf_token %} + + +
diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..79eb645 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,9 @@ +

Login

+
+ {% csrf_token %} + + + + + +
\ No newline at end of file diff --git a/templates/project_dashboard.html b/templates/project_dashboard.html new file mode 100644 index 0000000..7d2eea4 --- /dev/null +++ b/templates/project_dashboard.html @@ -0,0 +1,12 @@ +

Project: {{ project.name }}

+

{{ project.description }}

+

Initiator: {{ project.initiator.username }}

+

Manager: {{ project.manager.username if project.manager else "Not assigned" }}

+ +

Tasks

+
    + {% for task in tasks %} +
  • {{ task.name }} - {{ task.status }}
  • + {% endfor %} +
+Add Task diff --git a/templates/welcome.html b/templates/welcome.html new file mode 100644 index 0000000..b72922d --- /dev/null +++ b/templates/welcome.html @@ -0,0 +1,9 @@ + +

Welcome, {{ user.username }}

+

Your role: {{ user.profile.role }}

+ + \ No newline at end of file