From 136e807e7279a583be22b420d1c8df8f24b91212 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Thu, 11 Sep 2025 09:41:20 +0100 Subject: [PATCH 01/27] Fixed a permissions issue --- .../projects/templates/projects/partial_condition.html | 6 +++--- dashboard/projects/views.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dashboard/projects/templates/projects/partial_condition.html b/dashboard/projects/templates/projects/partial_condition.html index 8947d31..e75bcce 100644 --- a/dashboard/projects/templates/projects/partial_condition.html +++ b/dashboard/projects/templates/projects/partial_condition.html @@ -22,17 +22,17 @@ {{ condition.name }}
na Date: Thu, 11 Sep 2025 09:42:35 +0100 Subject: [PATCH 02/27] Bumped version --- README.md | 8 ++++---- rockcraft.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1ebaeab..71a6ef4 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ The application will run in a Kubernetes cluster, so we need a container image f Creating the container image might take several minutes, so this is a good point to take a break. When you return, you should see the following output: > ``` -> Packed dashboard_0.22_amd64.rock +> Packed dashboard_0.23_amd64.rock > ``` ### Create a charm @@ -109,10 +109,10 @@ Creating the charm might take several minutes, so this is another good point to ``` { name=deploy-dashboard } cd ~/dashboard rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ - oci-archive:dashboard_0.22_amd64.rock \ - docker://localhost:32000/dashboard:0.22 + oci-archive:dashboard_0.23_amd64.rock \ + docker://localhost:32000/dashboard:0.23 juju deploy ./charm/dashboard_ubuntu-22.04-amd64.charm \ - --resource django-app-image=localhost:32000/dashboard:0.22 + --resource django-app-image=localhost:32000/dashboard:0.23 ``` The `rockcraft.skopeo` command makes the container image available to Juju. diff --git a/rockcraft.yaml b/rockcraft.yaml index b4ebff7..d9348d2 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -2,7 +2,7 @@ name: dashboard # see https://documentation.ubuntu.com/rockcraft/en/1.8.0/explanation/bases/ # for more information about bases and using 'bare' bases for chiselled rocks base: ubuntu@22.04 # the base environment for this Django application -version: "0.22" # just for humans. Semantic versioning is recommended +version: "0.23" # just for humans. Semantic versioning is recommended summary: A summary of your Django application # 79 char long summary description: | Dashboard is a Django application to track quality and progress of multiple From 8dd5060ef7dedb4e94315858d36aae01faeea484 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Thu, 11 Sep 2025 19:17:39 +0100 Subject: [PATCH 03/27] Add more reload delays, fix another permissions bug --- dashboard/dashboard/static/base-styles.css | 13 +++++++++++++ .../templates/projects/partial_condition.html | 4 ++-- .../templates/projects/partial_objectivestatus.html | 2 +- .../templates/projects/partial_project_basics.html | 5 ++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/dashboard/dashboard/static/base-styles.css b/dashboard/dashboard/static/base-styles.css index d0a391b..dbcea28 100644 --- a/dashboard/dashboard/static/base-styles.css +++ b/dashboard/dashboard/static/base-styles.css @@ -1,5 +1,18 @@ body {font-family: Helvetica, sans-serif; font-size: 12px;} +div.auth a { + border-radius: 999em; + background: orange; + padding: 10px; + border: 1px solid black; + text-decoration: none; + font-weight: bold; + color: black; + display: inline-block; +} + +div.auth {margin-bottom: 1em;} + /* general table styles */ th {text-align: left;} diff --git a/dashboard/projects/templates/projects/partial_condition.html b/dashboard/projects/templates/projects/partial_condition.html index e75bcce..b0ab392 100644 --- a/dashboard/projects/templates/projects/partial_condition.html +++ b/dashboard/projects/templates/projects/partial_condition.html @@ -27,7 +27,7 @@ {% else %} class="condition not-applicable has-perms" hx-put="{% url 'projects:action_condition_toggle_not_applicable' condition.id %}" - hx-trigger="click" + hx-trigger="click delay:.5s" hx-target="closest tr" hx-swap="outerHTML" {% endif %}>na @@ -37,7 +37,7 @@ {% else %} class="condition candidate has-perms" hx-put="{% url 'projects:action_condition_toggle_candidate' condition.id %}" - hx-trigger="click" + hx-trigger="click delay:.5s" hx-target="closest tr" hx-swap="outerHTML" {% endif %}>candidate diff --git a/dashboard/projects/templates/projects/partial_objectivestatus.html b/dashboard/projects/templates/projects/partial_objectivestatus.html index f6638d1..ef7d328 100644 --- a/dashboard/projects/templates/projects/partial_objectivestatus.html +++ b/dashboard/projects/templates/projects/partial_objectivestatus.html @@ -13,7 +13,7 @@ {% else %} {% endif %} From 92c3c06e2f9d5fda7fc5b2a3ba8425ee171cec59 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Thu, 11 Sep 2025 19:18:48 +0100 Subject: [PATCH 04/27] Fixed status algorithm (again) --- dashboard/projects/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dashboard/projects/models.py b/dashboard/projects/models.py index 7a459c7..6dd5537 100644 --- a/dashboard/projects/models.py +++ b/dashboard/projects/models.py @@ -132,6 +132,7 @@ def achieved_level(self): objective=self.objective, condition__level=level, done=False, + not_applicable=False ).exists(): level_achieved = level else: From cfc961b5c434905aeea22e3c3a87b04fa9a077d2 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Thu, 11 Sep 2025 19:19:31 +0100 Subject: [PATCH 05/27] Added date picker for last review date --- dashboard/projects/forms.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/dashboard/projects/forms.py b/dashboard/projects/forms.py index 1453262..c1f5dba 100644 --- a/dashboard/projects/forms.py +++ b/dashboard/projects/forms.py @@ -1,9 +1,12 @@ from . import models -from django.forms import ModelForm -from django.forms import inlineformset_factory +from django import forms +class DatePickerInput(forms.DateInput): + input_type = 'date' + + +class ProjectDetailForm(forms.ModelForm): -class ProjectDetailForm(ModelForm): class Meta: model = models.Project fields = [ @@ -15,9 +18,14 @@ class Meta: "last_review", "last_review_status", ] + widgets = { + 'last_review' : DatePickerInput(), + } + + # last_review = forms.DateField(widget=forms.DateInput) -class ProjectObjectiveForm(ModelForm): +class ProjectObjectiveForm(forms.ModelForm): class Meta: model = models.ProjectObjective fields = ["unstarted_reason"] @@ -25,13 +33,13 @@ class Meta: help_texts = {"unstarted_reason": ""} -class ProjectObjectiveConditionForm(ModelForm): +class ProjectObjectiveConditionForm(forms.ModelForm): class Meta: model = models.ProjectObjectiveCondition fields = ["done"] -class CommitmentForm(ModelForm): +class CommitmentForm(forms.ModelForm): class Meta: model = models.Commitment fields = ["committed"] From e664637ad9ea570f1043d7ed6046a0dc45ec8ab7 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Thu, 11 Sep 2025 19:42:37 +0100 Subject: [PATCH 06/27] Added URL field, improved basic view --- dashboard/initial_data.yaml | 682 +----------------- dashboard/projects/admin.py | 2 +- dashboard/projects/forms.py | 1 + ...er_projectobjective_options_project_url.py | 22 + .../migrations/0011_alter_project_url.py | 18 + dashboard/projects/models.py | 1 + .../projects/partial_project_basics.html | 47 +- 7 files changed, 91 insertions(+), 682 deletions(-) create mode 100644 dashboard/projects/migrations/0010_alter_projectobjective_options_project_url.py create mode 100644 dashboard/projects/migrations/0011_alter_project_url.py diff --git a/dashboard/initial_data.yaml b/dashboard/initial_data.yaml index 9ff91dd..03db628 100644 --- a/dashboard/initial_data.yaml +++ b/dashboard/initial_data.yaml @@ -478,6 +478,7 @@ pk: 1 fields: name: Nuclear + url: '' group: 1 owner: A. Scientist driver: A. Technician @@ -488,16 +489,18 @@ pk: 2 fields: name: Chemistry + url: https://nuclear.nut group: 1 owner: A. Chemist driver: null - last_review: 2025-02-02 + last_review: null last_review_status: 1 agreement_status: 3 - model: projects.project pk: 3 fields: name: Early modern + url: '' group: 2 owner: A. Researcher driver: Some Musician @@ -508,6 +511,7 @@ pk: 4 fields: name: Deserts and oceans + url: '' group: 4 owner: A.N. Explorer driver: A. Traveller @@ -518,6 +522,7 @@ pk: 5 fields: name: Buses + url: '' group: 4 owner: Some Company driver: Bus Driver @@ -528,6 +533,7 @@ pk: 6 fields: name: Sea creatures + url: '' group: 1 owner: A.N. Ocean driver: null @@ -538,6 +544,7 @@ pk: 7 fields: name: Food packaging + url: '' group: 3 owner: A. Chemist driver: Some Packager @@ -548,6 +555,7 @@ pk: 9 fields: name: Aeroplanes + url: '' group: 4 owner: An Aviator driver: null @@ -558,6 +566,7 @@ pk: 10 fields: name: Restaurants + url: '' group: 5 owner: A. Gourmand driver: null @@ -568,6 +577,7 @@ pk: 11 fields: name: Kinds of pasta + url: '' group: 5 owner: A. Gourmand driver: null @@ -578,6 +588,7 @@ pk: 12 fields: name: Cafés + url: '' group: 5 owner: A. Director driver: null @@ -588,6 +599,7 @@ pk: 13 fields: name: Switches + url: '' group: 6 owner: A.N. Electrician driver: null @@ -598,6 +610,7 @@ pk: 14 fields: name: Plugs + url: '' group: 6 owner: A.N. Electrician driver: null @@ -608,6 +621,7 @@ pk: 15 fields: name: Sockets + url: '' group: 6 owner: A.N. Electrician driver: null @@ -618,6 +632,7 @@ pk: 16 fields: name: Photography + url: '' group: 7 owner: A. Lens driver: null @@ -628,6 +643,7 @@ pk: 17 fields: name: Sculpture + url: '' group: 7 owner: null driver: null @@ -638,6 +654,7 @@ pk: 18 fields: name: Collage + url: '' group: 7 owner: A.N. Artist driver: null @@ -35579,666 +35596,3 @@ project: 17 workcycle: 7 value: 0 -- model: auth.permission - pk: 1 - fields: - name: Can add project group - content_type: 1 - codename: add_projectgroup -- model: auth.permission - pk: 2 - fields: - name: Can change project group - content_type: 1 - codename: change_projectgroup -- model: auth.permission - pk: 3 - fields: - name: Can delete project group - content_type: 1 - codename: delete_projectgroup -- model: auth.permission - pk: 4 - fields: - name: Can view project group - content_type: 1 - codename: view_projectgroup -- model: auth.permission - pk: 5 - fields: - name: Can add project - content_type: 2 - codename: add_project -- model: auth.permission - pk: 6 - fields: - name: Can change project - content_type: 2 - codename: change_project -- model: auth.permission - pk: 7 - fields: - name: Can delete project - content_type: 2 - codename: delete_project -- model: auth.permission - pk: 8 - fields: - name: Can view project - content_type: 2 - codename: view_project -- model: auth.permission - pk: 9 - fields: - name: Can add project objective - content_type: 3 - codename: add_projectobjective -- model: auth.permission - pk: 10 - fields: - name: Can change project objective - content_type: 3 - codename: change_projectobjective -- model: auth.permission - pk: 11 - fields: - name: Can delete project objective - content_type: 3 - codename: delete_projectobjective -- model: auth.permission - pk: 12 - fields: - name: Can view project objective - content_type: 3 - codename: view_projectobjective -- model: auth.permission - pk: 13 - fields: - name: Can add project objective condition - content_type: 4 - codename: add_projectobjectivecondition -- model: auth.permission - pk: 14 - fields: - name: Can change project objective condition - content_type: 4 - codename: change_projectobjectivecondition -- model: auth.permission - pk: 15 - fields: - name: Can delete project objective condition - content_type: 4 - codename: delete_projectobjectivecondition -- model: auth.permission - pk: 16 - fields: - name: Can view project objective condition - content_type: 4 - codename: view_projectobjectivecondition -- model: auth.permission - pk: 17 - fields: - name: Can add qi - content_type: 5 - codename: add_qi -- model: auth.permission - pk: 18 - fields: - name: Can change qi - content_type: 5 - codename: change_qi -- model: auth.permission - pk: 19 - fields: - name: Can delete qi - content_type: 5 - codename: delete_qi -- model: auth.permission - pk: 20 - fields: - name: Can view qi - content_type: 5 - codename: view_qi -- model: auth.permission - pk: 21 - fields: - name: Can add level commitment - content_type: 6 - codename: add_commitment -- model: auth.permission - pk: 22 - fields: - name: Can change level commitment - content_type: 6 - codename: change_commitment -- model: auth.permission - pk: 23 - fields: - name: Can delete level commitment - content_type: 6 - codename: delete_commitment -- model: auth.permission - pk: 24 - fields: - name: Can view level commitment - content_type: 6 - codename: view_commitment -- model: auth.permission - pk: 25 - fields: - name: Can add level - content_type: 7 - codename: add_level -- model: auth.permission - pk: 26 - fields: - name: Can change level - content_type: 7 - codename: change_level -- model: auth.permission - pk: 27 - fields: - name: Can delete level - content_type: 7 - codename: delete_level -- model: auth.permission - pk: 28 - fields: - name: Can view level - content_type: 7 - codename: view_level -- model: auth.permission - pk: 29 - fields: - name: Can add objective - content_type: 8 - codename: add_objective -- model: auth.permission - pk: 30 - fields: - name: Can change objective - content_type: 8 - codename: change_objective -- model: auth.permission - pk: 31 - fields: - name: Can delete objective - content_type: 8 - codename: delete_objective -- model: auth.permission - pk: 32 - fields: - name: Can view objective - content_type: 8 - codename: view_objective -- model: auth.permission - pk: 33 - fields: - name: Can add objective group - content_type: 9 - codename: add_objectivegroup -- model: auth.permission - pk: 34 - fields: - name: Can change objective group - content_type: 9 - codename: change_objectivegroup -- model: auth.permission - pk: 35 - fields: - name: Can delete objective group - content_type: 9 - codename: delete_objectivegroup -- model: auth.permission - pk: 36 - fields: - name: Can view objective group - content_type: 9 - codename: view_objectivegroup -- model: auth.permission - pk: 37 - fields: - name: Can add project status - content_type: 10 - codename: add_projectstatus -- model: auth.permission - pk: 38 - fields: - name: Can change project status - content_type: 10 - codename: change_projectstatus -- model: auth.permission - pk: 39 - fields: - name: Can delete project status - content_type: 10 - codename: delete_projectstatus -- model: auth.permission - pk: 40 - fields: - name: Can view project status - content_type: 10 - codename: view_projectstatus -- model: auth.permission - pk: 41 - fields: - name: Can add work cycle - content_type: 11 - codename: add_workcycle -- model: auth.permission - pk: 42 - fields: - name: Can change work cycle - content_type: 11 - codename: change_workcycle -- model: auth.permission - pk: 43 - fields: - name: Can delete work cycle - content_type: 11 - codename: delete_workcycle -- model: auth.permission - pk: 44 - fields: - name: Can view work cycle - content_type: 11 - codename: view_workcycle -- model: auth.permission - pk: 45 - fields: - name: Can add condition - content_type: 12 - codename: add_condition -- model: auth.permission - pk: 46 - fields: - name: Can change condition - content_type: 12 - codename: change_condition -- model: auth.permission - pk: 47 - fields: - name: Can delete condition - content_type: 12 - codename: delete_condition -- model: auth.permission - pk: 48 - fields: - name: Can view condition - content_type: 12 - codename: view_condition -- model: auth.permission - pk: 49 - fields: - name: Can add reason - content_type: 13 - codename: add_reason -- model: auth.permission - pk: 50 - fields: - name: Can change reason - content_type: 13 - codename: change_reason -- model: auth.permission - pk: 51 - fields: - name: Can delete reason - content_type: 13 - codename: delete_reason -- model: auth.permission - pk: 52 - fields: - name: Can view reason - content_type: 13 - codename: view_reason -- model: auth.permission - pk: 53 - fields: - name: Can add log entry - content_type: 14 - codename: add_logentry -- model: auth.permission - pk: 54 - fields: - name: Can change log entry - content_type: 14 - codename: change_logentry -- model: auth.permission - pk: 55 - fields: - name: Can delete log entry - content_type: 14 - codename: delete_logentry -- model: auth.permission - pk: 56 - fields: - name: Can view log entry - content_type: 14 - codename: view_logentry -- model: auth.permission - pk: 57 - fields: - name: Can add permission - content_type: 15 - codename: add_permission -- model: auth.permission - pk: 58 - fields: - name: Can change permission - content_type: 15 - codename: change_permission -- model: auth.permission - pk: 59 - fields: - name: Can delete permission - content_type: 15 - codename: delete_permission -- model: auth.permission - pk: 60 - fields: - name: Can view permission - content_type: 15 - codename: view_permission -- model: auth.permission - pk: 61 - fields: - name: Can add group - content_type: 16 - codename: add_group -- model: auth.permission - pk: 62 - fields: - name: Can change group - content_type: 16 - codename: change_group -- model: auth.permission - pk: 63 - fields: - name: Can delete group - content_type: 16 - codename: delete_group -- model: auth.permission - pk: 64 - fields: - name: Can view group - content_type: 16 - codename: view_group -- model: auth.permission - pk: 65 - fields: - name: Can add user - content_type: 17 - codename: add_user -- model: auth.permission - pk: 66 - fields: - name: Can change user - content_type: 17 - codename: change_user -- model: auth.permission - pk: 67 - fields: - name: Can delete user - content_type: 17 - codename: delete_user -- model: auth.permission - pk: 68 - fields: - name: Can view user - content_type: 17 - codename: view_user -- model: auth.permission - pk: 69 - fields: - name: Can add content type - content_type: 18 - codename: add_contenttype -- model: auth.permission - pk: 70 - fields: - name: Can change content type - content_type: 18 - codename: change_contenttype -- model: auth.permission - pk: 71 - fields: - name: Can delete content type - content_type: 18 - codename: delete_contenttype -- model: auth.permission - pk: 72 - fields: - name: Can view content type - content_type: 18 - codename: view_contenttype -- model: auth.permission - pk: 73 - fields: - name: Can add session - content_type: 19 - codename: add_session -- model: auth.permission - pk: 74 - fields: - name: Can change session - content_type: 19 - codename: change_session -- model: auth.permission - pk: 75 - fields: - name: Can delete session - content_type: 19 - codename: delete_session -- model: auth.permission - pk: 76 - fields: - name: Can view session - content_type: 19 - codename: view_session -- model: auth.permission - pk: 77 - fields: - name: Can add agreement status - content_type: 20 - codename: add_agreementstatus -- model: auth.permission - pk: 78 - fields: - name: Can change agreement status - content_type: 20 - codename: change_agreementstatus -- model: auth.permission - pk: 79 - fields: - name: Can delete agreement status - content_type: 20 - codename: delete_agreementstatus -- model: auth.permission - pk: 80 - fields: - name: Can view agreement status - content_type: 20 - codename: view_agreementstatus -- model: auth.permission - pk: 81 - fields: - name: Can add agreement status - content_type: 14 - codename: add_agreementstatus -- model: auth.permission - pk: 82 - fields: - name: Can change agreement status - content_type: 14 - codename: change_agreementstatus -- model: auth.permission - pk: 83 - fields: - name: Can delete agreement status - content_type: 14 - codename: delete_agreementstatus -- model: auth.permission - pk: 84 - fields: - name: Can view agreement status - content_type: 14 - codename: view_agreementstatus -- model: auth.permission - pk: 85 - fields: - name: Can add log entry - content_type: 15 - codename: add_logentry -- model: auth.permission - pk: 86 - fields: - name: Can change log entry - content_type: 15 - codename: change_logentry -- model: auth.permission - pk: 87 - fields: - name: Can delete log entry - content_type: 15 - codename: delete_logentry -- model: auth.permission - pk: 88 - fields: - name: Can view log entry - content_type: 15 - codename: view_logentry -- model: auth.permission - pk: 89 - fields: - name: Can add permission - content_type: 16 - codename: add_permission -- model: auth.permission - pk: 90 - fields: - name: Can change permission - content_type: 16 - codename: change_permission -- model: auth.permission - pk: 91 - fields: - name: Can delete permission - content_type: 16 - codename: delete_permission -- model: auth.permission - pk: 92 - fields: - name: Can view permission - content_type: 16 - codename: view_permission -- model: auth.permission - pk: 93 - fields: - name: Can add group - content_type: 17 - codename: add_group -- model: auth.permission - pk: 94 - fields: - name: Can change group - content_type: 17 - codename: change_group -- model: auth.permission - pk: 95 - fields: - name: Can delete group - content_type: 17 - codename: delete_group -- model: auth.permission - pk: 96 - fields: - name: Can view group - content_type: 17 - codename: view_group -- model: auth.permission - pk: 97 - fields: - name: Can add user - content_type: 18 - codename: add_user -- model: auth.permission - pk: 98 - fields: - name: Can change user - content_type: 18 - codename: change_user -- model: auth.permission - pk: 99 - fields: - name: Can delete user - content_type: 18 - codename: delete_user -- model: auth.permission - pk: 100 - fields: - name: Can view user - content_type: 18 - codename: view_user -- model: auth.permission - pk: 101 - fields: - name: Can add content type - content_type: 19 - codename: add_contenttype -- model: auth.permission - pk: 102 - fields: - name: Can change content type - content_type: 19 - codename: change_contenttype -- model: auth.permission - pk: 103 - fields: - name: Can delete content type - content_type: 19 - codename: delete_contenttype -- model: auth.permission - pk: 104 - fields: - name: Can view content type - content_type: 19 - codename: view_contenttype -- model: auth.permission - pk: 105 - fields: - name: Can add session - content_type: 20 - codename: add_session -- model: auth.permission - pk: 106 - fields: - name: Can change session - content_type: 20 - codename: change_session -- model: auth.permission - pk: 107 - fields: - name: Can delete session - content_type: 20 - codename: delete_session -- model: auth.permission - pk: 108 - fields: - name: Can view session - content_type: 20 - codename: view_session -- model: auth.user - pk: 1 - fields: - password: pbkdf2_sha256$870000$wrLgDLm6LnfRMMx70aSref$+/mKdBWGs7a62LOYowsqKLv+QpJ71gCk/+cd3vPEuNY= - last_login: 2025-09-04 19:50:49.016048+00:00 - is_superuser: true - username: test - first_name: '' - last_name: '' - email: '' - is_staff: true - is_active: true - date_joined: 2025-02-02 00:10:09.583882+00:00 - groups: [] - user_permissions: [] diff --git a/dashboard/projects/admin.py b/dashboard/projects/admin.py index 09c4559..d95c3d8 100644 --- a/dashboard/projects/admin.py +++ b/dashboard/projects/admin.py @@ -113,7 +113,7 @@ class ProjectAdmin(admin.ModelAdmin): None, { "fields": ( - ("name", "group"), + ("name", "url", "group"), ("owner", "driver"), ("agreement_status"), ("last_review", "last_review_status"), diff --git a/dashboard/projects/forms.py b/dashboard/projects/forms.py index c1f5dba..5a8ebf7 100644 --- a/dashboard/projects/forms.py +++ b/dashboard/projects/forms.py @@ -11,6 +11,7 @@ class Meta: model = models.Project fields = [ "name", + "url", "group", "owner", "driver", diff --git a/dashboard/projects/migrations/0010_alter_projectobjective_options_project_url.py b/dashboard/projects/migrations/0010_alter_projectobjective_options_project_url.py new file mode 100644 index 0000000..30c25f9 --- /dev/null +++ b/dashboard/projects/migrations/0010_alter_projectobjective_options_project_url.py @@ -0,0 +1,22 @@ +# Generated by Django 5.1.5 on 2025-09-11 18:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("projects", "0009_rename_levelcommitment_commitment"), + ] + + operations = [ + migrations.AlterModelOptions( + name="projectobjective", + options={"ordering": ["objective"]}, + ), + migrations.AddField( + model_name="project", + name="url", + field=models.URLField(default="", null=True), + ), + ] diff --git a/dashboard/projects/migrations/0011_alter_project_url.py b/dashboard/projects/migrations/0011_alter_project_url.py new file mode 100644 index 0000000..6027339 --- /dev/null +++ b/dashboard/projects/migrations/0011_alter_project_url.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.5 on 2025-09-11 18:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("projects", "0010_alter_projectobjective_options_project_url"), + ] + + operations = [ + migrations.AlterField( + model_name="project", + name="url", + field=models.URLField(blank=True, default=""), + ), + ] diff --git a/dashboard/projects/models.py b/dashboard/projects/models.py index 6dd5537..08929bb 100644 --- a/dashboard/projects/models.py +++ b/dashboard/projects/models.py @@ -25,6 +25,7 @@ def __str__(self): class Project(models.Model): name = models.CharField(max_length=200) + url = models.URLField(blank=True, default="") group = models.ForeignKey( ProjectGroup, null=True, blank=True, on_delete=models.SET_NULL ) diff --git a/dashboard/projects/templates/projects/partial_project_basics.html b/dashboard/projects/templates/projects/partial_project_basics.html index 961f648..763885c 100644 --- a/dashboard/projects/templates/projects/partial_project_basics.html +++ b/dashboard/projects/templates/projects/partial_project_basics.html @@ -1,21 +1,34 @@ - +
-
-
+ {% if not user.is_authenticated %} + + {% endif %} - - {% for field in basics_form %} - - {% endfor %} -
{{ field.label_tag }}{{ field }}
+
+ + - {% if perms.projects.change_projectobjectivecondition %} - - {% endif %} + {% if project.url %} + + + + + {% endif %} - - + {% for field in basics_form %} + + {% endfor %} + +
Website{{ project.url }}
{{ field.label_tag }}{{ field }}
+ + {% if perms.projects.change_projectobjectivecondition %} + + {% endif %} + + +
+
\ No newline at end of file From a627455322b07a5fa34f7fbe965b68f63715fffb Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Thu, 11 Sep 2025 21:31:34 +0100 Subject: [PATCH 07/27] Added back tests for logged-in user --- dashboard/tests.py | 100 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 dashboard/tests.py diff --git a/dashboard/tests.py b/dashboard/tests.py new file mode 100644 index 0000000..0380f2e --- /dev/null +++ b/dashboard/tests.py @@ -0,0 +1,100 @@ +import pytest + +from playwright.sync_api import Page, expect + +from django.core.management import call_command +from django.urls import reverse +from django.contrib.auth.models import User +from django.test.client import Client +from django.conf import settings + +from projects.models import ( + Project, + ProjectObjective, + ProjectObjectiveCondition, + Commitment, +) + +from framework.models import Level + +@pytest.fixture(scope="session") +def django_db_setup(django_db_setup, django_db_blocker): + with django_db_blocker.unblock(): + call_command("loaddata", "initial_data.yaml") + +@pytest.fixture +def superuser(db): + return User.objects.create_superuser(username="test", password="test") + +def reverse_url( + live_server, viewname, urlconf=None, args=None, kwargs=None, current_app=None +): + end = reverse(viewname, urlconf, args, kwargs, current_app) + return f"{live_server.url}{end}" + +@pytest.fixture +def page(page, live_server, superuser): + + c = Client() + c.login(username="test", password="test") + session_cookie = c.cookies[settings.SESSION_COOKIE_NAME] + page.context.add_cookies([{ + "name": settings.SESSION_COOKIE_NAME, + "value": session_cookie.value, + "url": live_server.url, + }]) + return page + + +def test_toggling_conditions(live_server, page): + + url = reverse_url(live_server, viewname="projects:project", args=[1]) + page.goto(url) + + # check ProjectObjectiveCondition + # check that the expected conditions are represented on the page + assert ProjectObjectiveCondition.objects.count() == 697 + assert ProjectObjectiveCondition.objects.get(id=94).done == True + assert ProjectObjectiveCondition.objects.get(id=102).done == False + + # after toggling, the new conditions should be saved in the database + page.get_by_test_id("toggle_condition_94").uncheck() + page.get_by_test_id("toggle_condition_102").check() + + assert ProjectObjectiveCondition.objects.get(id=94).done == False + assert ProjectObjectiveCondition.objects.get(id=102).done == True + + # check Commitment + # check that the expected commitments are represented on the page + assert Commitment.objects.get(id=705).committed == False + assert Commitment.objects.get(id=474).committed == True + + page.get_by_test_id("toggle_commitment_705").check() + page.get_by_test_id("toggle_commitment_474").uncheck() + + assert Commitment.objects.get(id=705).committed == True + assert Commitment.objects.get(id=474).committed == False + + # check Status + # check that the expected conditions and status are represented on the page + assert ProjectObjectiveCondition.objects.get(id=1).done == True + assert ProjectObjectiveCondition.objects.get(id=6).done == True + assert ProjectObjectiveCondition.objects.get(id=10).done == False + assert ( + ProjectObjectiveCondition.objects.get(id=10).projectobjective().status() == None + ) + expect(page.get_by_test_id("projectobjective_status_1")).to_contain_text("") + + # check the remaining box to get to Started + page.get_by_test_id("toggle_condition_10").check() + assert ProjectObjectiveCondition.objects.get( + id=10 + ).projectobjective().status() == Level.objects.get(id=1) + expect(page.get_by_test_id("projectobjective_status_1")).to_contain_text("Started") + + # check one more to get to First results + page.get_by_test_id("toggle_condition_14").check() + assert ProjectObjectiveCondition.objects.get( + id=14 + ).projectobjective().status() == Level.objects.get(id=1) + expect(page.get_by_test_id("projectobjective_status_1")).to_contain_text("Started") From 6475a35bf3e66f5de0752b700709cf9c8d71af9f Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Thu, 11 Sep 2025 21:38:48 +0100 Subject: [PATCH 08/27] Updated CSS for candidate/na statuses --- dashboard/staticfiles/base-styles.css | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/dashboard/staticfiles/base-styles.css b/dashboard/staticfiles/base-styles.css index d0a391b..dbcea28 100644 --- a/dashboard/staticfiles/base-styles.css +++ b/dashboard/staticfiles/base-styles.css @@ -1,5 +1,18 @@ body {font-family: Helvetica, sans-serif; font-size: 12px;} +div.auth a { + border-radius: 999em; + background: orange; + padding: 10px; + border: 1px solid black; + text-decoration: none; + font-weight: bold; + color: black; + display: inline-block; +} + +div.auth {margin-bottom: 1em;} + /* general table styles */ th {text-align: left;} From 5ff906dafa88de66e607427e3141fa4b2a03e092 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Wed, 10 Sep 2025 21:09:31 +0100 Subject: [PATCH 09/27] Reformatted with Black --- dashboard/projects/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dashboard/projects/views.py b/dashboard/projects/views.py index b13e999..9bb467a 100644 --- a/dashboard/projects/views.py +++ b/dashboard/projects/views.py @@ -107,6 +107,7 @@ def status_projects_commitment(request, project_id): {"project": project, "current_commitments": current_commitments}, ) + @require_http_methods("GET") def status_projectobjective(request, projectobjective_id): From ce256fb2e2d15c94a45ee6b1156e0958f971f7be Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Wed, 10 Sep 2025 23:42:23 +0100 Subject: [PATCH 10/27] Added basic permission testing --- .../projects/templates/projects/partial_project_basics.html | 3 ++- dashboard/projects/views.py | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dashboard/projects/templates/projects/partial_project_basics.html b/dashboard/projects/templates/projects/partial_project_basics.html index 763885c..919c22b 100644 --- a/dashboard/projects/templates/projects/partial_project_basics.html +++ b/dashboard/projects/templates/projects/partial_project_basics.html @@ -31,4 +31,5 @@
-
\ No newline at end of file + + diff --git a/dashboard/projects/views.py b/dashboard/projects/views.py index 9bb467a..b13e999 100644 --- a/dashboard/projects/views.py +++ b/dashboard/projects/views.py @@ -107,7 +107,6 @@ def status_projects_commitment(request, project_id): {"project": project, "current_commitments": current_commitments}, ) - @require_http_methods("GET") def status_projectobjective(request, projectobjective_id): From c4ce0be9a20fe814c54f6575e93e43a08ca46262 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Thu, 11 Sep 2025 22:58:35 +0100 Subject: [PATCH 11/27] Removed unnecessary formset from template --- .../projects/partial_projectobjectives.html | 92 +++++++++---------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/dashboard/projects/templates/projects/partial_projectobjectives.html b/dashboard/projects/templates/projects/partial_projectobjectives.html index a59787e..67cd26d 100644 --- a/dashboard/projects/templates/projects/partial_projectobjectives.html +++ b/dashboard/projects/templates/projects/partial_projectobjectives.html @@ -8,66 +8,62 @@ - - {{ projectobjectives_formset.management_form }} + {% for projectobjectivegroup, projectobjectives in projectobjectivegroups %} + + {% if projectobjectivegroup %} + + + + {% endif %} + + {% for projectobjective in projectobjectives %} + + + + + + + + + + + + {% include "projects/partial_objectivestatus.html" %} + + {% for work_cycle in work_cycles %} + + {% endfor %} - {% for projectobjectivegroup, projectobjectives in projectobjectivegroups %} + - {% if projectobjectivegroup %} - - - - {% endif %} + {% regroup projectobjective.projectobjectiveconditions by level as levels %} + {% regroup projectobjective.commitments by level as commitments %} + {% pack levels commitments as level_collections %} - {% for projectobjective in projectobjectives %} + {% for condition, commitment in level_collections %} - - - + + - - - - - - - {% include "projects/partial_objectivestatus.html" %} - - {% for work_cycle in work_cycles %} - + {% for commitment in commitment.list %} + {% include "projects/partial_commitment.html" %} {% endfor %} - {% regroup projectobjective.projectobjectiveconditions by level as levels %} - {% regroup projectobjective.commitments by level as commitments %} - {% pack levels commitments as level_collections %} - - {% for condition, commitment in level_collections %} - - - - - {% for commitment in commitment.list %} - {% include "projects/partial_commitment.html" %} - {% endfor %} - - - - {% for condition in condition.list %} - {% include "projects/partial_condition.html" %} - {% endfor %} - + {% for condition in condition.list %} + {% include "projects/partial_condition.html" %} {% endfor %} + {% endfor %} {% endfor %} - + {% endfor %}
+ {{ projectobjectivegroup }} +
+ {{ projectobjective.objective }} +
+ {{ projectobjective.objective.description|default:"" }} +
{{ work_cycle }}
- {{ projectobjectivegroup }} -
- {{ projectobjective.objective }} -
{{ condition.grouper }}
- {{ projectobjective.objective.description|default:"" }} -
{{ work_cycle }}
{{ condition.grouper }}
From 7a7e846de5731e0b81c1803b8c6310ed6fa6b307 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Thu, 11 Sep 2025 23:04:54 +0100 Subject: [PATCH 12/27] Bumped version --- README.md | 8 ++++---- rockcraft.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 71a6ef4..9e26183 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ The application will run in a Kubernetes cluster, so we need a container image f Creating the container image might take several minutes, so this is a good point to take a break. When you return, you should see the following output: > ``` -> Packed dashboard_0.23_amd64.rock +> Packed dashboard_0.24_amd64.rock > ``` ### Create a charm @@ -109,10 +109,10 @@ Creating the charm might take several minutes, so this is another good point to ``` { name=deploy-dashboard } cd ~/dashboard rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ - oci-archive:dashboard_0.23_amd64.rock \ - docker://localhost:32000/dashboard:0.23 + oci-archive:dashboard_0.24_amd64.rock \ + docker://localhost:32000/dashboard:0.24 juju deploy ./charm/dashboard_ubuntu-22.04-amd64.charm \ - --resource django-app-image=localhost:32000/dashboard:0.23 + --resource django-app-image=localhost:32000/dashboard:0.24 ``` The `rockcraft.skopeo` command makes the container image available to Juju. diff --git a/rockcraft.yaml b/rockcraft.yaml index d9348d2..070933a 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -2,7 +2,7 @@ name: dashboard # see https://documentation.ubuntu.com/rockcraft/en/1.8.0/explanation/bases/ # for more information about bases and using 'bare' bases for chiselled rocks base: ubuntu@22.04 # the base environment for this Django application -version: "0.23" # just for humans. Semantic versioning is recommended +version: "0.24" # just for humans. Semantic versioning is recommended summary: A summary of your Django application # 79 char long summary description: | Dashboard is a Django application to track quality and progress of multiple From 0b3c352bf9907e629f2c1249a285190b99cc9b5e Mon Sep 17 00:00:00 2001 From: David Wilding Date: Fri, 12 Sep 2025 11:32:47 +0800 Subject: [PATCH 13/27] install playright before running tests --- dashboard/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/dashboard/Makefile b/dashboard/Makefile index 098ed8d..a4ce109 100644 --- a/dashboard/Makefile +++ b/dashboard/Makefile @@ -34,6 +34,7 @@ run: init test: install $(PIP) install -r requirements-dev.txt + $(VENV)/bin/python -m playwright install $(PYTEST) clean: From 86862e41d9fb58370d1c4b7ba6db955220b8d8fe Mon Sep 17 00:00:00 2001 From: David Wilding Date: Fri, 12 Sep 2025 11:52:38 +0800 Subject: [PATCH 14/27] add CI for running tests --- .github/workflows/tests.yaml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/workflows/tests.yaml diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..3165ebe --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,17 @@ +name: Tests + +on: + push: + branches: + - main + pull_request: + +jobs: + app-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Run tests + run: | + cd dashboard + make test From 1582127a839b62ba9c5d08336ba9d03aeb0912d5 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Fri, 12 Sep 2025 11:53:43 +0800 Subject: [PATCH 15/27] fix testing deps --- dashboard/requirements-dev.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dashboard/requirements-dev.txt b/dashboard/requirements-dev.txt index ec7b1a2..3333980 100644 --- a/dashboard/requirements-dev.txt +++ b/dashboard/requirements-dev.txt @@ -1,5 +1,4 @@ pytest==8.3.4 -pytest_django==4.10.0 +pytest-django==4.10.0 pytest-playwright==0.7.0 -pytest-django==4.9.0 pytest-env==1.1.5 From e6ec7d0dcb140d875f808455e5b3268a98880382 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Fri, 12 Sep 2025 08:06:27 +0100 Subject: [PATCH 16/27] Improved frontend tests Now loads initial_data.yaml that includes built-in superuser and driver users. --- dashboard/initial_data.yaml | 56 +++++++++++++++++++++++++++++-------- dashboard/tests.py | 9 ++---- 2 files changed, 47 insertions(+), 18 deletions(-) diff --git a/dashboard/initial_data.yaml b/dashboard/initial_data.yaml index 03db628..07295de 100644 --- a/dashboard/initial_data.yaml +++ b/dashboard/initial_data.yaml @@ -678,7 +678,7 @@ fields: project: 3 objective: 1 - unstarted_reason: null + unstarted_reason: 2 - model: projects.projectobjective pk: 4 fields: @@ -1790,7 +1790,7 @@ objective: 1 condition: 1 done: true - not_applicable: false + not_applicable: true candidate: false - model: projects.projectobjectivecondition pk: 2 @@ -1798,8 +1798,8 @@ project: 2 objective: 1 condition: 1 - done: true - not_applicable: false + done: false + not_applicable: true candidate: false - model: projects.projectobjectivecondition pk: 3 @@ -1807,7 +1807,7 @@ project: 3 objective: 1 condition: 1 - done: true + done: false not_applicable: false candidate: false - model: projects.projectobjectivecondition @@ -1825,9 +1825,9 @@ project: 2 objective: 1 condition: 2 - done: true + done: false not_applicable: false - candidate: false + candidate: true - model: projects.projectobjectivecondition pk: 6 fields: @@ -1836,7 +1836,7 @@ condition: 2 done: true not_applicable: false - candidate: false + candidate: true - model: projects.projectobjectivecondition pk: 7 fields: @@ -1987,9 +1987,9 @@ project: 3 objective: 1 condition: 6 - done: true + done: false not_applicable: false - candidate: false + candidate: true - model: projects.projectobjectivecondition pk: 24 fields: @@ -26111,7 +26111,7 @@ project: 1 objective: 1 level: 1 - committed: false + committed: true - model: projects.commitment pk: 2984 fields: @@ -26287,7 +26287,7 @@ project: 3 objective: 1 level: 1 - committed: false + committed: true - model: projects.commitment pk: 3006 fields: @@ -35596,3 +35596,35 @@ project: 17 workcycle: 7 value: 0 +- model: auth.user + pk: 1 + fields: + password: pbkdf2_sha256$870000$hT8SyrMNY9MlEyhUPlumxb$VfCsnVHt11Gxk2ERdWf0dbrzv3CwsxFt2HOS/pZzCNk= + last_login: 2025-09-12 06:21:50.612823+00:00 + is_superuser: true + username: superuser + first_name: '' + last_name: '' + email: '' + is_staff: true + is_active: true + date_joined: 2025-09-12 06:19:30.852188+00:00 + groups: [] + user_permissions: [] +- model: auth.user + pk: 2 + fields: + password: pbkdf2_sha256$870000$bj2XMSwNv6RSmyDjdE9I8C$6VPMgYpLnHI4QoBjbIyDkmDa5BuihSG1vQAZ3casJVg= + last_login: 2025-09-12 06:20:53+00:00 + is_superuser: false + username: driver + first_name: '' + last_name: '' + email: '' + is_staff: true + is_active: true + date_joined: 2025-09-12 06:19:47+00:00 + groups: [] + user_permissions: + - 22 + - 14 diff --git a/dashboard/tests.py b/dashboard/tests.py index 0380f2e..5a92f56 100644 --- a/dashboard/tests.py +++ b/dashboard/tests.py @@ -22,10 +22,6 @@ def django_db_setup(django_db_setup, django_db_blocker): with django_db_blocker.unblock(): call_command("loaddata", "initial_data.yaml") -@pytest.fixture -def superuser(db): - return User.objects.create_superuser(username="test", password="test") - def reverse_url( live_server, viewname, urlconf=None, args=None, kwargs=None, current_app=None ): @@ -33,10 +29,10 @@ def reverse_url( return f"{live_server.url}{end}" @pytest.fixture -def page(page, live_server, superuser): +def page(page, live_server): c = Client() - c.login(username="test", password="test") + c.login(username="superuser", password="superuser") session_cookie = c.cookies[settings.SESSION_COOKIE_NAME] page.context.add_cookies([{ "name": settings.SESSION_COOKIE_NAME, @@ -80,6 +76,7 @@ def test_toggling_conditions(live_server, page): assert ProjectObjectiveCondition.objects.get(id=1).done == True assert ProjectObjectiveCondition.objects.get(id=6).done == True assert ProjectObjectiveCondition.objects.get(id=10).done == False + assert ProjectObjectiveCondition.objects.get(id=10).not_applicable == False assert ( ProjectObjectiveCondition.objects.get(id=10).projectobjective().status() == None ) From b3157ca9c9b2aca619da58df32fd30d31dc5955b Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Fri, 12 Sep 2025 08:08:39 +0100 Subject: [PATCH 17/27] Update dashboard/projects/templates/projects/partial_project_basics.html Co-authored-by: Dave Wilding --- .../projects/templates/projects/partial_project_basics.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/projects/templates/projects/partial_project_basics.html b/dashboard/projects/templates/projects/partial_project_basics.html index 919c22b..bfe1c1b 100644 --- a/dashboard/projects/templates/projects/partial_project_basics.html +++ b/dashboard/projects/templates/projects/partial_project_basics.html @@ -15,7 +15,7 @@ {% if project.url %} Website - {{ project.url }} + {{ project.url }} {% endif %} From 93bbfcb1211f95664c0441776042526e64aa20ee Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Fri, 12 Sep 2025 19:13:50 +0100 Subject: [PATCH 18/27] Updated initial_data.yaml --- dashboard/initial_data.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dashboard/initial_data.yaml b/dashboard/initial_data.yaml index 07295de..1726ebe 100644 --- a/dashboard/initial_data.yaml +++ b/dashboard/initial_data.yaml @@ -478,7 +478,7 @@ pk: 1 fields: name: Nuclear - url: '' + url: https://nuclear.org group: 1 owner: A. Scientist driver: A. Technician @@ -489,7 +489,7 @@ pk: 2 fields: name: Chemistry - url: https://nuclear.nut + url: https://chemistry.org group: 1 owner: A. Chemist driver: null From f84d6e33580f9670e254eff9d7a8cc17cbac6c80 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Fri, 12 Sep 2025 19:14:30 +0100 Subject: [PATCH 19/27] Removed project objective inlines from project admin --- dashboard/projects/admin.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/dashboard/projects/admin.py b/dashboard/projects/admin.py index d95c3d8..c8c3b28 100644 --- a/dashboard/projects/admin.py +++ b/dashboard/projects/admin.py @@ -50,21 +50,6 @@ def has_delete_permission(self, request, obj): return False -class ProjectObjectiveInline(admin.TabularInline): - model = ProjectObjective - max_num = 0 - can_delete = False - fields = ("name", "unstarted_reason") - readonly_fields = ["name"] - exclude = ["objective", "description", "status"] - - def has_add_permission(self, request, obj): - return False - - def has_delete_permission(self, request, obj): - return False - - @admin.register(ProjectObjective) class ProjectObjectiveAdmin(admin.ModelAdmin): readonly_fields = ["project", "objective", "status"] @@ -95,9 +80,7 @@ def change_view(self, request, object_id, form_url="", extra_context=None): @admin.register(Project) class ProjectAdmin(admin.ModelAdmin): - inlines = [ - ProjectObjectiveInline, - ] + list_display = [ "name", "owner", From 81c9ca79b134fe54b310b164f668738d6596322e Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Fri, 12 Sep 2025 20:46:14 +0100 Subject: [PATCH 20/27] Added log in/out and other URLs to project page --- dashboard/dashboard/settings.py | 3 ++ .../templates/registration/login.html | 12 +++++ dashboard/dashboard/urls.py | 1 + .../projects/partial_project_basics.html | 5 -- .../projects/templates/projects/project.html | 48 +++++++++++++------ 5 files changed, 50 insertions(+), 19 deletions(-) create mode 100644 dashboard/dashboard/templates/registration/login.html diff --git a/dashboard/dashboard/settings.py b/dashboard/dashboard/settings.py index d61b14a..57414b6 100644 --- a/dashboard/dashboard/settings.py +++ b/dashboard/dashboard/settings.py @@ -131,3 +131,6 @@ # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +LOGIN_REDIRECT_URL = "projects:project_list" +LOGOUT_REDIRECT_URL = "projects:project_list" \ No newline at end of file diff --git a/dashboard/dashboard/templates/registration/login.html b/dashboard/dashboard/templates/registration/login.html new file mode 100644 index 0000000..a55f300 --- /dev/null +++ b/dashboard/dashboard/templates/registration/login.html @@ -0,0 +1,12 @@ +{% extends "base.html" %} + +{% block content %} + +

Log in

+
+ {% csrf_token %} + {{ form }} + +
+ +{% endblock %} diff --git a/dashboard/dashboard/urls.py b/dashboard/dashboard/urls.py index ba26b91..ba60a4f 100644 --- a/dashboard/dashboard/urls.py +++ b/dashboard/dashboard/urls.py @@ -4,6 +4,7 @@ urlpatterns = [ path("admin/doc/", include("django.contrib.admindocs.urls")), path("admin/", admin.site.urls), + path("accounts/", include("django.contrib.auth.urls")), path("__reload__/", include("django_browser_reload.urls")), path("", include("projects.urls")), ] diff --git a/dashboard/projects/templates/projects/partial_project_basics.html b/dashboard/projects/templates/projects/partial_project_basics.html index bfe1c1b..b8aa035 100644 --- a/dashboard/projects/templates/projects/partial_project_basics.html +++ b/dashboard/projects/templates/projects/partial_project_basics.html @@ -1,9 +1,4 @@
- - {% if not user.is_authenticated %} - - {% endif %} -
{{ project }} +
-
-
-
-
-
+ -
{% include "projects/partial_project_basics.html" %}
+

{{ project }}

-
+
+
+
+
-
{% include "projects/partial_project_commitments.html" %}
+
{% include "projects/partial_project_basics.html" %}
-
+
- {% include "projects/partial_projectobjectives.html" %} +
{% include "projects/partial_project_commitments.html" %}
+
+ + {% include "projects/partial_projectobjectives.html" %} + +
-
-
+ +
-
+ {% endblock content %} From 1580bae6bf73b75dcabbb6056901f1eb5d364077 Mon Sep 17 00:00:00 2001 From: Daniele Procida Date: Mon, 15 Sep 2025 07:38:39 +0100 Subject: [PATCH 21/27] Bumped version --- README.md | 8 ++++---- rockcraft.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 9e26183..a8aba4e 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ The application will run in a Kubernetes cluster, so we need a container image f Creating the container image might take several minutes, so this is a good point to take a break. When you return, you should see the following output: > ``` -> Packed dashboard_0.24_amd64.rock +> Packed dashboard_0.25_amd64.rock > ``` ### Create a charm @@ -109,10 +109,10 @@ Creating the charm might take several minutes, so this is another good point to ``` { name=deploy-dashboard } cd ~/dashboard rockcraft.skopeo --insecure-policy copy --dest-tls-verify=false \ - oci-archive:dashboard_0.24_amd64.rock \ - docker://localhost:32000/dashboard:0.24 + oci-archive:dashboard_0.25_amd64.rock \ + docker://localhost:32000/dashboard:0.25 juju deploy ./charm/dashboard_ubuntu-22.04-amd64.charm \ - --resource django-app-image=localhost:32000/dashboard:0.24 + --resource django-app-image=localhost:32000/dashboard:0.25 ``` The `rockcraft.skopeo` command makes the container image available to Juju. diff --git a/rockcraft.yaml b/rockcraft.yaml index 070933a..d346957 100644 --- a/rockcraft.yaml +++ b/rockcraft.yaml @@ -2,7 +2,7 @@ name: dashboard # see https://documentation.ubuntu.com/rockcraft/en/1.8.0/explanation/bases/ # for more information about bases and using 'bare' bases for chiselled rocks base: ubuntu@22.04 # the base environment for this Django application -version: "0.24" # just for humans. Semantic versioning is recommended +version: "0.25" # just for humans. Semantic versioning is recommended summary: A summary of your Django application # 79 char long summary description: | Dashboard is a Django application to track quality and progress of multiple From ca54915efdf14c5c8d35c9503fe5b0de422a60c2 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 15 Sep 2025 18:03:32 +0800 Subject: [PATCH 22/27] add delays in more places (temporary workaround) --- dashboard/tests.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dashboard/tests.py b/dashboard/tests.py index 69e3fbe..185b228 100644 --- a/dashboard/tests.py +++ b/dashboard/tests.py @@ -58,7 +58,7 @@ def test_toggling_conditions(live_server, page): # after toggling, the new conditions should be saved in the database page.get_by_test_id("toggle_condition_94").uncheck() page.get_by_test_id("toggle_condition_102").check() - time.sleep(0.5) # Temporary workaround. Find a better solution. + time.sleep(1) # Temporary workaround. Find a better solution. assert ProjectObjectiveCondition.objects.get(id=94).done == False assert ProjectObjectiveCondition.objects.get(id=102).done == True @@ -70,7 +70,7 @@ def test_toggling_conditions(live_server, page): page.get_by_test_id("toggle_commitment_705").check() page.get_by_test_id("toggle_commitment_474").uncheck() - time.sleep(0.5) # Temporary workaround. Find a better solution. + time.sleep(1) # Temporary workaround. Find a better solution. assert Commitment.objects.get(id=705).committed == True assert Commitment.objects.get(id=474).committed == False @@ -88,6 +88,7 @@ def test_toggling_conditions(live_server, page): # check the remaining box to get to Started page.get_by_test_id("toggle_condition_10").check() + time.sleep(1) # Temporary workaround. Find a better solution. assert ProjectObjectiveCondition.objects.get( id=10 ).projectobjective().status() == Level.objects.get(id=1) @@ -95,6 +96,7 @@ def test_toggling_conditions(live_server, page): # check one more to get to First results page.get_by_test_id("toggle_condition_14").check() + time.sleep(1) # Temporary workaround. Find a better solution. assert ProjectObjectiveCondition.objects.get( id=14 ).projectobjective().status() == Level.objects.get(id=1) From aeb3e95e8d7b9f555a1c4a016f5f9839e4016617 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 15 Sep 2025 18:06:02 +0800 Subject: [PATCH 23/27] try very long delays (for debugging --- dashboard/tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dashboard/tests.py b/dashboard/tests.py index 185b228..f2595e4 100644 --- a/dashboard/tests.py +++ b/dashboard/tests.py @@ -88,7 +88,7 @@ def test_toggling_conditions(live_server, page): # check the remaining box to get to Started page.get_by_test_id("toggle_condition_10").check() - time.sleep(1) # Temporary workaround. Find a better solution. + time.sleep(10) # Temporary workaround. Find a better solution. assert ProjectObjectiveCondition.objects.get( id=10 ).projectobjective().status() == Level.objects.get(id=1) @@ -96,7 +96,7 @@ def test_toggling_conditions(live_server, page): # check one more to get to First results page.get_by_test_id("toggle_condition_14").check() - time.sleep(1) # Temporary workaround. Find a better solution. + time.sleep(10) # Temporary workaround. Find a better solution. assert ProjectObjectiveCondition.objects.get( id=14 ).projectobjective().status() == Level.objects.get(id=1) From 6df08a9667d7ad5753ae5e344520ffa248403986 Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 15 Sep 2025 18:10:18 +0800 Subject: [PATCH 24/27] compromise on 5-second delays --- dashboard/tests.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dashboard/tests.py b/dashboard/tests.py index f2595e4..3d72b07 100644 --- a/dashboard/tests.py +++ b/dashboard/tests.py @@ -58,7 +58,7 @@ def test_toggling_conditions(live_server, page): # after toggling, the new conditions should be saved in the database page.get_by_test_id("toggle_condition_94").uncheck() page.get_by_test_id("toggle_condition_102").check() - time.sleep(1) # Temporary workaround. Find a better solution. + time.sleep(5) # Temporary workaround. Find a better solution. assert ProjectObjectiveCondition.objects.get(id=94).done == False assert ProjectObjectiveCondition.objects.get(id=102).done == True @@ -70,7 +70,7 @@ def test_toggling_conditions(live_server, page): page.get_by_test_id("toggle_commitment_705").check() page.get_by_test_id("toggle_commitment_474").uncheck() - time.sleep(1) # Temporary workaround. Find a better solution. + time.sleep(5) # Temporary workaround. Find a better solution. assert Commitment.objects.get(id=705).committed == True assert Commitment.objects.get(id=474).committed == False @@ -88,7 +88,7 @@ def test_toggling_conditions(live_server, page): # check the remaining box to get to Started page.get_by_test_id("toggle_condition_10").check() - time.sleep(10) # Temporary workaround. Find a better solution. + time.sleep(5) # Temporary workaround. Find a better solution. assert ProjectObjectiveCondition.objects.get( id=10 ).projectobjective().status() == Level.objects.get(id=1) @@ -96,7 +96,7 @@ def test_toggling_conditions(live_server, page): # check one more to get to First results page.get_by_test_id("toggle_condition_14").check() - time.sleep(10) # Temporary workaround. Find a better solution. + time.sleep(5) # Temporary workaround. Find a better solution. assert ProjectObjectiveCondition.objects.get( id=14 ).projectobjective().status() == Level.objects.get(id=1) From 191fbf074a6bd20ba505867b9a05e286583cc0ed Mon Sep 17 00:00:00 2001 From: David Wilding Date: Mon, 15 Sep 2025 22:06:27 +0800 Subject: [PATCH 25/27] match settings.py update in rock version --- dashboard/dashboard/settings.py | 2 +- dashboard_rock_patch/dashboard/settings.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dashboard/dashboard/settings.py b/dashboard/dashboard/settings.py index 57414b6..8a5ba7e 100644 --- a/dashboard/dashboard/settings.py +++ b/dashboard/dashboard/settings.py @@ -133,4 +133,4 @@ DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" LOGIN_REDIRECT_URL = "projects:project_list" -LOGOUT_REDIRECT_URL = "projects:project_list" \ No newline at end of file +LOGOUT_REDIRECT_URL = "projects:project_list" diff --git a/dashboard_rock_patch/dashboard/settings.py b/dashboard_rock_patch/dashboard/settings.py index cf51e1a..de23c4c 100644 --- a/dashboard_rock_patch/dashboard/settings.py +++ b/dashboard_rock_patch/dashboard/settings.py @@ -151,3 +151,6 @@ # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" + +LOGIN_REDIRECT_URL = "projects:project_list" +LOGOUT_REDIRECT_URL = "projects:project_list" From c509c8ca056122f24b656c2c2005e32b0d53003a Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 16 Sep 2025 14:31:02 +0800 Subject: [PATCH 26/27] match login form layout with project form --- .../dashboard/templates/registration/login.html | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/dashboard/dashboard/templates/registration/login.html b/dashboard/dashboard/templates/registration/login.html index a55f300..9597548 100644 --- a/dashboard/dashboard/templates/registration/login.html +++ b/dashboard/dashboard/templates/registration/login.html @@ -5,8 +5,19 @@

Log in

{% csrf_token %} - {{ form }} - + + + + {% for field in form %} + + {% endfor %} + +
{{ field.label_tag }}{{ field }}
+ +

+ +

+
{% endblock %} From b972a19cf76bb67223add199941130bddd75290a Mon Sep 17 00:00:00 2001 From: David Wilding Date: Tue, 16 Sep 2025 14:31:39 +0800 Subject: [PATCH 27/27] send users back to the project page on logout --- dashboard/projects/templates/projects/project.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/projects/templates/projects/project.html b/dashboard/projects/templates/projects/project.html index 928615a..180869a 100644 --- a/dashboard/projects/templates/projects/project.html +++ b/dashboard/projects/templates/projects/project.html @@ -23,7 +23,7 @@
  • Admin
  • Logged in as {{ user.username }}
  • -
    + {% csrf_token %}