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" %}
+
{% 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 @@
+
+
- 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 @@