diff --git a/.makim.yaml b/.makim.yaml
index 23ef7b1..9f401fa 100644
--- a/.makim.yaml
+++ b/.makim.yaml
@@ -121,7 +121,7 @@ groups:
CWD=$(pwd)
CMD_FLAG="{{ '--check' if args.check else '' }} --no-input"
- for app in 'sites' 'growth_plan_linker' ''; do
+ for app in 'sites' 'growth_plan_linker' 'projects' 'linker' ''; do
echo -e "\nMaking migration files for: '${app}' ...\n"
CMD="python manage.py makemigrations ${CMD_FLAG} ${app}"
sugar run --service app --cmd $CMD
diff --git a/src/config/settings/base.py b/src/config/settings/base.py
index c16590a..4725433 100644
--- a/src/config/settings/base.py
+++ b/src/config/settings/base.py
@@ -89,7 +89,13 @@
'drf_spectacular',
]
-LOCAL_APPS = ['growth_plan_linker.users', 'growth_plan_linker']
+LOCAL_APPS = [
+ 'growth_plan_linker.users',
+ 'growth_plan_linker',
+ 'projects.apps.ProjectsConfig',
+ 'linker.apps.LinkerConfig',
+ 'growth_plan.apps.GrowthPlanConfig',
+]
# https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps
INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS
diff --git a/src/config/urls.py b/src/config/urls.py
index 357f32e..5a6849e 100644
--- a/src/config/urls.py
+++ b/src/config/urls.py
@@ -11,6 +11,11 @@
from drf_spectacular.views import SpectacularSwaggerView
from rest_framework.authtoken.views import obtain_auth_token
+from growth_plan_linker.urls import (
+ urlpatterns as urlpatterns_growth_plan_linker,
+)
+
+
urlpatterns = [
path(
'', TemplateView.as_view(template_name='pages/home.html'), name='home'
@@ -31,7 +36,8 @@
# ...
# Media files
*static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT),
-]
+] + urlpatterns_growth_plan_linker
+
if settings.DEBUG:
# Static file serving when using Gunicorn + Uvicorn for local web socket development
urlpatterns += staticfiles_urlpatterns()
diff --git a/src/growth_plan/__init__.py b/src/growth_plan/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/growth_plan/admin.py b/src/growth_plan/admin.py
new file mode 100644
index 0000000..846f6b4
--- /dev/null
+++ b/src/growth_plan/admin.py
@@ -0,0 +1 @@
+# Register your models here.
diff --git a/src/growth_plan/apps.py b/src/growth_plan/apps.py
new file mode 100644
index 0000000..e177afb
--- /dev/null
+++ b/src/growth_plan/apps.py
@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class GrowthPlanConfig(AppConfig):
+ default_auto_field = 'django.db.models.BigAutoField'
+ name = 'growth_plan'
diff --git a/src/growth_plan/forms.py b/src/growth_plan/forms.py
new file mode 100644
index 0000000..89422e5
--- /dev/null
+++ b/src/growth_plan/forms.py
@@ -0,0 +1,31 @@
+from django import forms
+
+from .models import GrowthPlanItem
+
+
+class GrowthPlanItemForm(forms.ModelForm):
+ class Meta:
+ model = GrowthPlanItem
+ fields = [
+ 'title',
+ 'description',
+ 'start_date',
+ 'end_date',
+ 'progress_percentage',
+ ]
+ widgets = {
+ 'title': forms.TextInput(attrs={'class': 'form-control'}),
+ 'description': forms.Textarea(attrs={'class': 'form-control'}),
+ # Specify the date format for start_date and end_date fields
+ 'start_date': forms.DateInput(
+ format='%Y-%m-%d',
+ attrs={'class': 'form-control', 'type': 'date'},
+ ),
+ 'end_date': forms.DateInput(
+ format='%Y-%m-%d',
+ attrs={'class': 'form-control', 'type': 'date'},
+ ),
+ 'progress_percentage': forms.NumberInput(
+ attrs={'class': 'form-control'}
+ ),
+ }
diff --git a/src/growth_plan/migrations/0001_initial.py b/src/growth_plan/migrations/0001_initial.py
new file mode 100644
index 0000000..3c3dd4a
--- /dev/null
+++ b/src/growth_plan/migrations/0001_initial.py
@@ -0,0 +1,33 @@
+# Generated by Django 5.0.2 on 2024-02-21 01:59
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='GrowthPlanItem',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('title', models.CharField(max_length=255, verbose_name='Title')),
+ ('description', models.TextField(verbose_name='Description')),
+ ('start_date', models.DateField(verbose_name='Start Date')),
+ ('end_date', models.DateField(verbose_name='End Date')),
+ ('progress_percentage', models.PositiveIntegerField(default=0, help_text='Enter a number between 0 and 100.', verbose_name='Progress Percentage')),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='growth_plan_items', to=settings.AUTH_USER_MODEL, verbose_name='User')),
+ ],
+ options={
+ 'verbose_name': 'Growth Plan Item',
+ 'verbose_name_plural': 'Growth Plan Items',
+ },
+ ),
+ ]
diff --git a/src/growth_plan/migrations/__init__.py b/src/growth_plan/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/growth_plan/models.py b/src/growth_plan/models.py
new file mode 100644
index 0000000..cf8f315
--- /dev/null
+++ b/src/growth_plan/models.py
@@ -0,0 +1,28 @@
+from django.conf import settings
+from django.db import models
+from django.utils.translation import gettext_lazy as _
+
+
+class GrowthPlanItem(models.Model):
+ user = models.ForeignKey(
+ settings.AUTH_USER_MODEL,
+ on_delete=models.CASCADE,
+ related_name='growth_plan_items',
+ verbose_name=_('User'),
+ )
+ title = models.CharField(max_length=255, verbose_name=_('Title'))
+ description = models.TextField(verbose_name=_('Description'))
+ start_date = models.DateField(verbose_name=_('Start Date'))
+ end_date = models.DateField(verbose_name=_('End Date'))
+ progress_percentage = models.PositiveIntegerField(
+ default=0,
+ verbose_name=_('Progress Percentage'),
+ help_text=_('Enter a number between 0 and 100.'),
+ )
+
+ class Meta:
+ verbose_name = _('Growth Plan Item')
+ verbose_name_plural = _('Growth Plan Items')
+
+ def __str__(self):
+ return self.title
diff --git a/src/growth_plan/templates/growth-plan-item-confirm-delete.html b/src/growth_plan/templates/growth-plan-item-confirm-delete.html
new file mode 100644
index 0000000..f46edf3
--- /dev/null
+++ b/src/growth_plan/templates/growth-plan-item-confirm-delete.html
@@ -0,0 +1,21 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+
+ Confirm Delete
+
+
+ Are you sure you want to delete "{{ object.title }}"?
+
+
+
+{% endblock content %}
diff --git a/src/growth_plan/templates/growth-plan-item-form.html b/src/growth_plan/templates/growth-plan-item-form.html
new file mode 100644
index 0000000..a4928b0
--- /dev/null
+++ b/src/growth_plan/templates/growth-plan-item-form.html
@@ -0,0 +1,43 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+
+
+ {{ title }} Growth Plan Item
+
+
+
+{% endblock content %}
diff --git a/src/growth_plan/templates/my-growth-plan.html b/src/growth_plan/templates/my-growth-plan.html
new file mode 100644
index 0000000..c8b9de8
--- /dev/null
+++ b/src/growth_plan/templates/my-growth-plan.html
@@ -0,0 +1,45 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+
+ My Growth Plan
+
+
+ {% for item in items %}
+
+
+
+
+ {{ item.title }}
+
+
+ {{ item.start_date }} to {{ item.end_date }}
+
+
+ {{ item.description }}
+
+
+
+
+
+ {% empty %}
+
+ No growth plan items found.
+
+ {% endfor %}
+
+
Add New Item
+
+{% endblock content %}
diff --git a/src/growth_plan/templates/review-growth-plan-list.html b/src/growth_plan/templates/review-growth-plan-list.html
new file mode 100644
index 0000000..507cf00
--- /dev/null
+++ b/src/growth_plan/templates/review-growth-plan-list.html
@@ -0,0 +1,38 @@
+{% extends 'base.html' %}
+
+{% block content %}
+
+
+ Growth Plan Review
+
+
+ {% for item in items %}
+
+
+
+
+ {{ item.title }}
+
+
+ Assigned to: {{ item.user.get_full_name }}
+
+
+ {{ item.description }}
+
+
+ Progress: {{ item.progress_percentage }}%
+
+
+ Duration: {{ item.start_date }} to {{ item.end_date }}
+
+
+
+
+ {% empty %}
+
+ No growth plan items to review.
+
+ {% endfor %}
+
+
+{% endblock content %}
diff --git a/src/growth_plan/tests.py b/src/growth_plan/tests.py
new file mode 100644
index 0000000..a39b155
--- /dev/null
+++ b/src/growth_plan/tests.py
@@ -0,0 +1 @@
+# Create your tests here.
diff --git a/src/growth_plan/urls.py b/src/growth_plan/urls.py
new file mode 100644
index 0000000..4724fa5
--- /dev/null
+++ b/src/growth_plan/urls.py
@@ -0,0 +1,35 @@
+from django.urls import path
+
+from .views import (
+ GrowthPlanItemCreateView,
+ GrowthPlanItemDeleteView,
+ GrowthPlanItemUpdateView,
+ review_growth_plan_list,
+ user_growth_plan_list,
+)
+
+urlpatterns = [
+ path(
+ 'my-growth-plan/', user_growth_plan_list, name='user-growth-plan-list'
+ ),
+ path(
+ 'review-growth-plans/',
+ review_growth_plan_list,
+ name='review-growth-plan-list',
+ ),
+ path(
+ 'create/',
+ GrowthPlanItemCreateView.as_view(),
+ name='growth-plan-item-create',
+ ),
+ path(
+ 'edit//',
+ GrowthPlanItemUpdateView.as_view(),
+ name='growth-plan-item-edit',
+ ),
+ path(
+ 'delete//',
+ GrowthPlanItemDeleteView.as_view(),
+ name='growth-plan-item-delete',
+ ),
+]
diff --git a/src/growth_plan/views.py b/src/growth_plan/views.py
new file mode 100644
index 0000000..be03660
--- /dev/null
+++ b/src/growth_plan/views.py
@@ -0,0 +1,82 @@
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth.models import User
+from django.http import HttpResponseForbidden
+from django.shortcuts import render
+from django.urls import reverse_lazy
+from django.utils.decorators import method_decorator
+from django.views.generic import DeleteView, UpdateView
+from django.views.generic.edit import CreateView
+
+from .forms import GrowthPlanItemForm
+from .models import GrowthPlanItem
+
+
+@login_required
+def user_growth_plan_list(request):
+ # List only the logged-in user's growth plan items
+ items = GrowthPlanItem.objects.filter(user=request.user)
+ return render(request, 'my-growth-plan.html', {'items': items})
+
+
+@login_required
+def review_growth_plan_list(request):
+ # Accessible only to supervisors; list users' growth
+ # plan items linked through projects
+ if not request.user.supervised_projects.exists():
+ return HttpResponseForbidden()
+ supervised_users = User.objects.filter(
+ projects__supervisors=request.user
+ ).distinct()
+ items = GrowthPlanItem.objects.filter(
+ user__in=supervised_users
+ ).select_related('user')
+ return render(request, 'review-growth-plan-list.html', {'items': items})
+
+
+@method_decorator(login_required, name='dispatch')
+class GrowthPlanItemCreateView(CreateView):
+ model = GrowthPlanItem
+ form_class = GrowthPlanItemForm
+ template_name = 'growth-plan-item-form.html'
+
+ def form_valid(self, form):
+ form.instance.user = (
+ self.request.user
+ ) # Set the user to the current user
+ return super().form_valid(form)
+
+ def get_success_url(self):
+ return reverse_lazy(
+ 'user-growth-plan-list'
+ ) # Redirect to the user's growth plan list
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['title'] = 'Create'
+ return context
+
+
+class GrowthPlanItemUpdateView(UpdateView):
+ model = GrowthPlanItem
+ form_class = GrowthPlanItemForm
+ template_name = 'growth-plan-item-form.html'
+ success_url = reverse_lazy('user-growth-plan-list')
+
+ def get_queryset(self):
+ # Users can only edit their own growth plan items
+ return GrowthPlanItem.objects.filter(user=self.request.user)
+
+ def get_context_data(self, **kwargs):
+ context = super().get_context_data(**kwargs)
+ context['title'] = 'Edit'
+ return context
+
+
+class GrowthPlanItemDeleteView(DeleteView):
+ model = GrowthPlanItem
+ template_name = 'growth-plan-item-confirm-delete.html'
+ success_url = reverse_lazy('user-growth-plan-list')
+
+ def get_queryset(self):
+ # Users can only delete their own growth plan items
+ return GrowthPlanItem.objects.filter(user=self.request.user)
diff --git a/src/growth_plan_linker/forms.py b/src/growth_plan_linker/forms.py
index 6375d0a..63f4650 100644
--- a/src/growth_plan_linker/forms.py
+++ b/src/growth_plan_linker/forms.py
@@ -2,7 +2,7 @@
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
-from .models import Feedback, Link, Profile, Project
+from .models import Profile
class UserRegisterForm(UserCreationForm):
@@ -27,46 +27,3 @@ class ProfileUpdateForm(forms.ModelForm):
class Meta:
model = Profile
fields = [] # Specify the fields you want to include, e.g., []
-
-
-class ProjectForm(forms.ModelForm):
- class Meta:
- model = Project
- fields = [
- 'name',
- 'supervisors',
- ] # Adjust according to your Project model fields
-
-
-class LinkForm(forms.ModelForm):
- class Meta:
- model = Link
- fields = [
- 'person_one',
- 'person_two',
- 'supervisor',
- 'periodicity',
- 'times',
- ]
- widgets = {
- 'person_one': forms.Select(attrs={'class': 'form-control'}),
- 'person_two': forms.Select(attrs={'class': 'form-control'}),
- 'supervisor': forms.Select(attrs={'class': 'form-control'}),
- 'periodicity': forms.Select(
- choices=Link.PERIODICITY_CHOICES,
- attrs={'class': 'form-control'},
- ),
- 'times': forms.NumberInput(attrs={'class': 'form-control'}),
- }
-
-
-class FeedbackForm(forms.ModelForm):
- class Meta:
- model = Feedback
- fields = ['content', 'link']
- widgets = {
- 'content': forms.Textarea(
- attrs={'class': 'form-control', 'rows': 3}
- ),
- 'link': forms.Select(attrs={'class': 'form-control'}),
- }
diff --git a/src/growth_plan_linker/migrations/0001_initial.py b/src/growth_plan_linker/migrations/0001_initial.py
index 31523d2..0c2e984 100644
--- a/src/growth_plan_linker/migrations/0001_initial.py
+++ b/src/growth_plan_linker/migrations/0001_initial.py
@@ -1,4 +1,4 @@
-# Generated by Django 5.0.2 on 2024-02-20 01:13
+# Generated by Django 5.0.2 on 2024-02-20 20:34
import django.db.models.deletion
from django.conf import settings
@@ -10,44 +10,17 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
+ ('projects', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
- migrations.CreateModel(
- name='Link',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('periodicity', models.CharField(max_length=50)),
- ('times', models.IntegerField()),
- ('person_one', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='person_one_links', to=settings.AUTH_USER_MODEL)),
- ('person_two', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='person_two_links', to=settings.AUTH_USER_MODEL)),
- ('supervisor', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='supervised_links', to=settings.AUTH_USER_MODEL)),
- ],
- ),
- migrations.CreateModel(
- name='Feedback',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('content', models.TextField()),
- ('timestamp', models.DateTimeField(auto_now_add=True)),
- ('link', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='feedback', to='growth_plan_linker.link')),
- ],
- ),
- migrations.CreateModel(
- name='Project',
- fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('name', models.CharField(max_length=100)),
- ('supervisors', models.ManyToManyField(related_name='supervised_projects', to=settings.AUTH_USER_MODEL)),
- ],
- ),
migrations.CreateModel(
name='Profile',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('projects', models.ManyToManyField(related_name='participants', to='projects.project')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
- ('projects', models.ManyToManyField(related_name='participants', to='growth_plan_linker.project')),
],
),
]
diff --git a/src/growth_plan_linker/models.py b/src/growth_plan_linker/models.py
index 728b84a..00bdcce 100644
--- a/src/growth_plan_linker/models.py
+++ b/src/growth_plan_linker/models.py
@@ -8,62 +8,15 @@ class Profile(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL, on_delete=models.CASCADE
)
- projects = models.ManyToManyField('Project', related_name='participants')
+ projects = models.ManyToManyField(
+ 'projects.Project', related_name='participants'
+ )
# Add any additional fields for your Person model here
def __str__(self):
return self.user.username
-class Project(models.Model):
- name = models.CharField(max_length=100)
- # Assuming you want to keep a relation to track project supervisors
- supervisors = models.ManyToManyField(
- settings.AUTH_USER_MODEL, related_name='supervised_projects'
- )
-
- def __str__(self):
- return self.name
-
-
-class Link(models.Model):
- person_one = models.ForeignKey(
- settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE,
- related_name='person_one_links',
- )
- person_two = models.ForeignKey(
- settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE,
- related_name='person_two_links',
- )
- supervisor = models.ForeignKey(
- settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE,
- related_name='supervised_links',
- )
- periodicity = models.CharField(max_length=50)
- times = models.IntegerField()
-
- def __str__(self):
- return f'{self.person_one.username} <-> {self.person_two.username}'
-
-
-class Feedback(models.Model):
- content = models.TextField()
- link = models.ForeignKey(
- Link, on_delete=models.CASCADE, related_name='feedback'
- )
- timestamp = models.DateTimeField(auto_now_add=True)
-
- def __str__(self):
- return (
- f'Feedback from {self.link.person_one.username} to '
- f'{self.link.person_two.username} on '
- f'{self.timestamp.strftime("%Y-%m-%d %H:%M:%S")}'
- )
-
-
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_user_profile(sender, instance, created, **kwargs):
if created:
diff --git a/src/growth_plan_linker/templates/account/email_confirm.html b/src/growth_plan_linker/templates/account/email_confirm.html
index 2f2fe14..1996349 100644
--- a/src/growth_plan_linker/templates/account/email_confirm.html
+++ b/src/growth_plan_linker/templates/account/email_confirm.html
@@ -4,8 +4,7 @@
{% load account %}
-{% block
- head_title %}
+{% block head_title %}
{% translate "Confirm E-mail Address" %}
{% endblock head_title %}
@@ -14,8 +13,7 @@
{% translate "Confirm E-mail Address" %}
{% if confirmation %}
- {% user_display confirmation.email_address.user as
- user_display %}
+ {% user_display confirmation.email_address.user as user_display %}
{% blocktranslate with confirmation.email_address.email as email %}Please
confirm that {{ email }} is an e-mail address
diff --git a/src/growth_plan_linker/templates/base.html b/src/growth_plan_linker/templates/base.html
index 1f74938..9c1d592 100644
--- a/src/growth_plan_linker/templates/base.html
+++ b/src/growth_plan_linker/templates/base.html
@@ -38,6 +38,27 @@
type="text/css" />
+
{% compress css %}
@@ -95,7 +116,15 @@
href="{% url 'about' %}">About
{% if request.user.is_authenticated %}
-
+
+ {% translate "Projects" %}
+
+
+ {% translate "Growth Plan" %}
+
+
{% translate "My Profile" %}
@@ -214,7 +243,7 @@
- Copyright © Open Science Labs 2024
+ Copyright © Open Science Labs 2024