From 081deff02a3849851a9638544360f828408d3160 Mon Sep 17 00:00:00 2001 From: Sam Bland Date: Fri, 3 Jul 2026 14:15:08 +0100 Subject: [PATCH 1/6] Implement a shareable url of a skills profile that can be viewed when not logged in. --- .../templates/main/shared-skills-profile.html | 36 +++++++++++++++++++ .../create_user_skill_profile_link.html | 36 +++++++++++++++++++ main/templates/main/user-skills-profile.html | 2 ++ main/urls.py | 5 +++ main/views/page_views.py | 24 +++++++++++++ 5 files changed, 103 insertions(+) create mode 100644 main/templates/main/shared-skills-profile.html create mode 100644 main/templates/main/snippets/create_user_skill_profile_link.html diff --git a/main/templates/main/shared-skills-profile.html b/main/templates/main/shared-skills-profile.html new file mode 100644 index 00000000..49144bc1 --- /dev/null +++ b/main/templates/main/shared-skills-profile.html @@ -0,0 +1,36 @@ +{% extends "main/base.page.html" %} +{% block body_class %} + bg-secondary +{% endblock body_class %} +{% load static %} +{% load crispy_forms_tags %} +{% load django_bootstrap5 %} +{% block title %} + Digital Research Competencies Framework +{% endblock title %} +{% block content %} + +
+
+ +
+
+
+

Shared Skills profile

+

+ Explore a shared skill level profile in the Digital Research Competencies Framework + using the interactive Skills Wheel. Each segment represents a competency, + with size according to the self-assessed level. +

+
+ {% include "main/snippets/skill-wheel.html" %} + + {% include "main/snippets/export_user_skill_profile.html" %} + + {% include "main/snippets/create_user_skill_profile_link.html" %} +
+
+
+
+
+{% endblock content %} diff --git a/main/templates/main/snippets/create_user_skill_profile_link.html b/main/templates/main/snippets/create_user_skill_profile_link.html new file mode 100644 index 00000000..834ca492 --- /dev/null +++ b/main/templates/main/snippets/create_user_skill_profile_link.html @@ -0,0 +1,36 @@ + + diff --git a/main/templates/main/user-skills-profile.html b/main/templates/main/user-skills-profile.html index 9acac910..b327b4c0 100644 --- a/main/templates/main/user-skills-profile.html +++ b/main/templates/main/user-skills-profile.html @@ -34,6 +34,8 @@

Skills profile

{% include "main/snippets/skill-wheel.html" %} {% include "main/snippets/export_user_skill_profile.html" %} + + {% include "main/snippets/create_user_skill_profile_link.html" %} diff --git a/main/urls.py b/main/urls.py index 476593a9..28b87056 100644 --- a/main/urls.py +++ b/main/urls.py @@ -66,4 +66,9 @@ path("get-involved/", views.GetInvolvedPageView.as_view(), name="get_involved"), path("events/", views.EventsPageView.as_view(), name="events"), path("framework-json/", views.FrameworkView.as_view(), name="framework_json"), + path( + "view_skill_profile/", + views.ViewSkillProfilePageView.as_view(), + name="view_skill_profile", + ), ] diff --git a/main/views/page_views.py b/main/views/page_views.py index b224ec98..6ce2ceb7 100644 --- a/main/views/page_views.py +++ b/main/views/page_views.py @@ -318,3 +318,27 @@ class LicensingPageView(GitHubMarkdownPageView): github_raw_url = ( "https://raw.githubusercontent.com/direct-framework/.github/main/LICENSING.md" ) + + +class ViewSkillProfilePageView(TemplateView): + """View that renders a shared skill profile based on query parameters.""" + + template_name = "main/shared-skills-profile.html" + + def get_context_data(self, **kwargs: Mapping[str, Any]) -> dict[str, Any]: + """Add skill profile data from query parameters to the context.""" + context = super().get_context_data(**kwargs) + chart_data_json = self.request.GET.get("chart_data", "[]") + skill_levels_json = self.request.GET.get("skill_levels", "[]") + + try: + # TODO: We should validate this data more robustly before rendering + # it in the template + context["chart_data"] = json.loads(chart_data_json) + context["skill_levels"] = json.loads(skill_levels_json) + except json.JSONDecodeError: + logger.error("Invalid JSON in query parameters") + context["chart_data"] = [] + context["skill_levels"] = [] + + return context From ef9f50e0f75e39803f3aa053c713782069ea48a3 Mon Sep 17 00:00:00 2001 From: Sam Bland Date: Fri, 3 Jul 2026 14:15:08 +0100 Subject: [PATCH 2/6] Add nh3 sanitization to json data in ViewSkillProfileView --- main/views/page_views.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/main/views/page_views.py b/main/views/page_views.py index 6ce2ceb7..757ab8a6 100644 --- a/main/views/page_views.py +++ b/main/views/page_views.py @@ -328,12 +328,11 @@ class ViewSkillProfilePageView(TemplateView): def get_context_data(self, **kwargs: Mapping[str, Any]) -> dict[str, Any]: """Add skill profile data from query parameters to the context.""" context = super().get_context_data(**kwargs) - chart_data_json = self.request.GET.get("chart_data", "[]") - skill_levels_json = self.request.GET.get("skill_levels", "[]") + + chart_data_json = nh3.clean(self.request.GET.get("chart_data", "[]")) + skill_levels_json = nh3.clean(self.request.GET.get("skill_levels", "[]")) try: - # TODO: We should validate this data more robustly before rendering - # it in the template context["chart_data"] = json.loads(chart_data_json) context["skill_levels"] = json.loads(skill_levels_json) except json.JSONDecodeError: From dabbe8d4046227f0ee91173b4e6a6b531cdefbc7 Mon Sep 17 00:00:00 2001 From: Sam Bland Date: Fri, 3 Jul 2026 14:15:08 +0100 Subject: [PATCH 3/6] Add the standard view test for ViewSkillProfileView --- tests/main/test_main_views.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/main/test_main_views.py b/tests/main/test_main_views.py index cf79baf0..997a2928 100644 --- a/tests/main/test_main_views.py +++ b/tests/main/test_main_views.py @@ -565,3 +565,12 @@ class TestLicensingPageView(TemplateOkMixin): def _get_url(self): return reverse("licensing") + + +class TestViewSkillProfilePageView(TemplateOkMixin): + """Test suite for the ViewSkillProfilePageView.""" + + _template_name = "main/shared-skills-profile.html" + + def _get_url(self): + return reverse("view_skill_profile") From 313181467865fc8a16b4c5a4df1a0fac891ddc88 Mon Sep 17 00:00:00 2001 From: Sam Bland Date: Fri, 3 Jul 2026 14:15:08 +0100 Subject: [PATCH 4/6] Move skills profile sharing to dropdown - Now includes copy link, open link and export csv --- .../templates/main/shared-skills-profile.html | 32 +++++++++++-------- .../create_user_skill_profile_link.html | 7 ++-- .../share_skill_profile_dropdown.html | 30 +++++++++++++++++ main/templates/main/user-skills-profile.html | 32 +++++++++++-------- 4 files changed, 70 insertions(+), 31 deletions(-) create mode 100644 main/templates/main/snippets/share_skill_profile_dropdown.html diff --git a/main/templates/main/shared-skills-profile.html b/main/templates/main/shared-skills-profile.html index 49144bc1..c92d1c02 100644 --- a/main/templates/main/shared-skills-profile.html +++ b/main/templates/main/shared-skills-profile.html @@ -16,20 +16,24 @@
-

Shared Skills profile

-

- Explore a shared skill level profile in the Digital Research Competencies Framework - using the interactive Skills Wheel. Each segment represents a competency, - with size according to the self-assessed level. -

-
- {% include "main/snippets/skill-wheel.html" %} - - {% include "main/snippets/export_user_skill_profile.html" %} - - {% include "main/snippets/create_user_skill_profile_link.html" %} -
-
+
+
+

Shared Skills profile

+
+
+ + {% include "main/snippets/share_skill_profile_dropdown.html" %} +
+

+ Explore a shared skill level profile in the Digital Research Competencies Framework + using the interactive Skills Wheel. Each segment represents a competency, + with size according to the self-assessed level. +

+
+ {% include "main/snippets/skill-wheel.html" %} +
+ +
diff --git a/main/templates/main/snippets/create_user_skill_profile_link.html b/main/templates/main/snippets/create_user_skill_profile_link.html index 834ca492..099b8142 100644 --- a/main/templates/main/snippets/create_user_skill_profile_link.html +++ b/main/templates/main/snippets/create_user_skill_profile_link.html @@ -12,6 +12,7 @@ // On document load we convert the skill levels and chart data into a query string and set it as the href of the export link. document.addEventListener('DOMContentLoaded', function () { const exportSkillProfileLink = document.querySelector('.export-skill-profile-link'); + const exportSkillProfileLinkCopyButton = document.querySelector('.export-skill-profile-link-copy'); const skillLevels = {{ skill_levels|safe }}; const charts = {{ chart_data|safe }}; @@ -21,10 +22,10 @@ queryParams.set('skill_levels', JSON.stringify(skillLevels)); queryParams.set('chart_data', JSON.stringify(charts)); - exportSkillProfileLink.href = `${window.location.origin}/view_skill_profile?${queryParams.toString()}`; const shareableLink = `${window.location.origin}/view_skill_profile?${queryParams.toString()}`; - // exportSkillProfileLink.textContent = `Shareable Link: ${shareableLink}`; - exportSkillProfileLink.addEventListener('click', function (event) { + exportSkillProfileLink.href = shareableLink; + exportSkillProfileLinkCopyButton.href = shareableLink; + exportSkillProfileLinkCopyButton.addEventListener('click', function (event) { event.preventDefault(); navigator.clipboard.writeText(shareableLink).then(function() { alert('Shareable link copied to clipboard!'); diff --git a/main/templates/main/snippets/share_skill_profile_dropdown.html b/main/templates/main/snippets/share_skill_profile_dropdown.html new file mode 100644 index 00000000..40d5e9d5 --- /dev/null +++ b/main/templates/main/snippets/share_skill_profile_dropdown.html @@ -0,0 +1,30 @@ + + diff --git a/main/templates/main/user-skills-profile.html b/main/templates/main/user-skills-profile.html index b327b4c0..a3e3f0af 100644 --- a/main/templates/main/user-skills-profile.html +++ b/main/templates/main/user-skills-profile.html @@ -24,20 +24,24 @@
-

Skills profile

-

- Explore your skill levels in the Digital Research Competencies Framework - using the interactive Skills Wheel. Each segment represents a competency, - with size according to your self-assessed level. -

-
- {% include "main/snippets/skill-wheel.html" %} - - {% include "main/snippets/export_user_skill_profile.html" %} - - {% include "main/snippets/create_user_skill_profile_link.html" %} -
-
+
+
+

Skills profile

+
+
+ + {% include "main/snippets/share_skill_profile_dropdown.html" %} +
+

+ Explore your skill levels in the Digital Research Competencies Framework + using the interactive Skills Wheel. Each segment represents a competency, + with size according to your self-assessed level. +

+
+ {% include "main/snippets/skill-wheel.html" %} +
+ +
From 4582cc0ac9c443088ee827308d0d4c20be640398 Mon Sep 17 00:00:00 2001 From: Sam Bland Date: Fri, 3 Jul 2026 14:15:08 +0100 Subject: [PATCH 5/6] Implement error message for invalid query data in shared skills profile --- main/templates/main/shared-skills-profile.html | 5 +++++ main/views/page_views.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/main/templates/main/shared-skills-profile.html b/main/templates/main/shared-skills-profile.html index c92d1c02..c81cd855 100644 --- a/main/templates/main/shared-skills-profile.html +++ b/main/templates/main/shared-skills-profile.html @@ -30,6 +30,11 @@

Shared Skills profile

with size according to the self-assessed level.

+ {% if error_message %} + + {% else %} + {% include "main/snippets/skill-wheel.html" %} + {% endif %} {% include "main/snippets/skill-wheel.html" %} diff --git a/main/views/page_views.py b/main/views/page_views.py index 757ab8a6..bd140e8a 100644 --- a/main/views/page_views.py +++ b/main/views/page_views.py @@ -336,8 +336,8 @@ def get_context_data(self, **kwargs: Mapping[str, Any]) -> dict[str, Any]: context["chart_data"] = json.loads(chart_data_json) context["skill_levels"] = json.loads(skill_levels_json) except json.JSONDecodeError: - logger.error("Invalid JSON in query parameters") context["chart_data"] = [] context["skill_levels"] = [] + context["error_message"] = "Invalid data provided in query parameters." return context From ac9be18704c3672534dff0440aa7b13c20a1a2d9 Mon Sep 17 00:00:00 2001 From: Sam Bland Date: Fri, 3 Jul 2026 14:15:08 +0100 Subject: [PATCH 6/6] Remove double include skill wheel snippet --- main/templates/main/shared-skills-profile.html | 1 - 1 file changed, 1 deletion(-) diff --git a/main/templates/main/shared-skills-profile.html b/main/templates/main/shared-skills-profile.html index c81cd855..2be6e0aa 100644 --- a/main/templates/main/shared-skills-profile.html +++ b/main/templates/main/shared-skills-profile.html @@ -35,7 +35,6 @@

Shared Skills profile

{% else %} {% include "main/snippets/skill-wheel.html" %} {% endif %} - {% include "main/snippets/skill-wheel.html" %}