first commit

This commit is contained in:
= 2024-09-08 08:51:55 -04:00
commit e40c9e3d8f
30 changed files with 536 additions and 0 deletions

67
Read.me Normal file
View File

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

35
app.js Normal file
View File

@ -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 (
<div>
<h1>Projects</h1>
<ul>
{projects.map(project => (
<li key={project.id}>{project.name}</li>
))}
</ul>
<h1>Tasks</h1>
<ul>
{tasks.map(task => (
<li key={task.id}>{task.name}</li>
))}
</ul>
</div>
);
}
export default App;

0
main/__init__.py Normal file
View File

3
main/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

12
main/api_views.py Normal file
View File

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

8
main/apps.py Normal file
View File

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

12
main/forms.py Normal file
View File

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

View File

48
main/models.py Normal file
View File

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

12
main/serializers.py Normal file
View File

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

10
main/signals.py Normal file
View File

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

3
main/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

19
main/urls.py Normal file
View File

@ -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/<int:project_id>/', project_dashboard, name='project_dashboard'),
path('projects/<int:project_id>/tasks/create/', create_task, name='create_task'),
]

64
main/views.py Normal file
View File

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

22
manage.py Normal file
View File

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

View File

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,16 @@
"""
ASGI config for wbsportal project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wbsportal.settings')
application = get_asgi_application()

View File

@ -0,0 +1,128 @@
"""
Django settings for wbsportal project.
Generated by 'django-admin startproject' using Django 4.1.4.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.1/ref/settings/
"""
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-e#$wxb+=br=3u(ju%$dzru!=6p4myj^!3fyb8-@d@y+9dx0od2'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'wbsportal.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'wbsportal.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.1/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'p00003db',
'USER': 'p00003dbuser',
'PASSWORD': 'Express.123',
'HOST': 'localhost', # Set to 'localhost' or your database server IP
'PORT': '5432', # Default port for PostgreSQL
}
}
# Password validation
# https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.1/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

View File

@ -0,0 +1,8 @@
# project_management/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('main.urls')),
]

View File

@ -0,0 +1,16 @@
"""
WSGI config for wbsportal project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.1/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'wbsportal.settings')
application = get_wsgi_application()

View File

@ -0,0 +1,6 @@
<h2>Create New Project</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Create Project</button>
</form>

View File

@ -0,0 +1,6 @@
<h2>Create Task for Project: {{ project.name }}</h2>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Create Task</button>
</form>

View File

@ -0,0 +1,11 @@
<!-- templates/load_project.html -->
<h2>Load Existing Project</h2>
<form method="post">
{% csrf_token %}
<select name="project_id">
{% for project in projects %}
<option value="{{ project.id }}">{{ project.name }}</option>
{% endfor %}
</select>
<button type="submit">Load Project</button>
</form>

9
templates/login.html Normal file
View File

@ -0,0 +1,9 @@
<h2>Login</h2>
<form method="post">
{% csrf_token %}
<label for="username">Username:</label>
<input type="text" id="username" name="username">
<label for="password">Password:</label>
<input type="password" id="password" name="password">
<button type="submit">Login</button>
</form>

View File

@ -0,0 +1,12 @@
<h2>Project: {{ project.name }}</h2>
<p>{{ project.description }}</p>
<p>Initiator: {{ project.initiator.username }}</p>
<p>Manager: {{ project.manager.username if project.manager else "Not assigned" }}</p>
<h3>Tasks</h3>
<ul>
{% for task in tasks %}
<li>{{ task.name }} - {{ task.status }}</li>
{% endfor %}
</ul>
<a href="{% url 'create_task' project.id %}">Add Task</a>

9
templates/welcome.html Normal file
View File

@ -0,0 +1,9 @@
<!-- templates/welcome.html -->
<h2>Welcome, {{ user.username }}</h2>
<p>Your role: {{ user.profile.role }}</p>
<ul>
<li><a href="{% url 'create_project' %}">Create New Project</a></li>
<li><a href="#">Load Existing Project</a></li>
<li><a href="{% url 'logout' %}">Exit</a></li>
</ul>