diff --git a/main/templates/main/snippets/export_user_skill_profile.html b/main/templates/main/snippets/export_user_skill_profile.html
index 0665c0e2..d3b724ef 100644
--- a/main/templates/main/snippets/export_user_skill_profile.html
+++ b/main/templates/main/snippets/export_user_skill_profile.html
@@ -1,14 +1,14 @@
-
-
+
diff --git a/main/templates/main/snippets/share_skill_profile_dropdown.html b/main/templates/main/snippets/share_skill_profile_dropdown.html
index 10639308..f08fb608 100644
--- a/main/templates/main/snippets/share_skill_profile_dropdown.html
+++ b/main/templates/main/snippets/share_skill_profile_dropdown.html
@@ -1,4 +1,9 @@
-
+
Copy Link
- {% include "main/snippets/create_user_skill_profile_link.html" %}
-
-
-
- {% include "main/snippets/export_user_skill_profile.html" %}
+ {% include "main/snippets/create_user_skill_profile_link.html" %}
+ {% if allow_export %}
+
+ Export as CSV
+
+
+ Export as JSON
+
+ {% endif %}
diff --git a/main/urls.py b/main/urls.py
index 28b87056..a04cadaa 100644
--- a/main/urls.py
+++ b/main/urls.py
@@ -36,6 +36,16 @@
path("skills-profile/", views.SkillProfileView.as_view(), name="skills_profile"),
path("profile/", views.UserUpdateView.as_view(), name="profile"),
path("overview/", views.AccountOverviewView.as_view(), name="account-overview"),
+ path(
+ "download-skills-data/csv/",
+ views.DownloadUserSkillDataCsvView.as_view(),
+ name="download-csv-data-link",
+ ),
+ path(
+ "download-skills-data/json/",
+ views.DownloadUserSkillDataJSONView.as_view(),
+ name="download-json-data-link",
+ ),
]
policies_patterns = [
diff --git a/main/views/account_views.py b/main/views/account_views.py
index fa26b89e..706413bc 100644
--- a/main/views/account_views.py
+++ b/main/views/account_views.py
@@ -104,6 +104,10 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
context["skill_levels"] = dumps(
list(SkillLevel.objects.values("level", "name"))
)
+
+ context["allow_export"] = (
+ True # Allow exporting skill profile data as CSV or JSON
+ )
return context
diff --git a/main/views/data_views.py b/main/views/data_views.py
index 85d03ecd..a1d9ea1e 100644
--- a/main/views/data_views.py
+++ b/main/views/data_views.py
@@ -1,9 +1,12 @@
"""Views for getting data from the framework database."""
-from django.http import HttpRequest, JsonResponse
+import csv
+
+from django.http import HttpRequest, HttpResponse, JsonResponse
from django.views import View
from ..io_resources import export_framework
+from ..models import UserSkill
class FrameworkView(View):
@@ -23,3 +26,82 @@ def get(self, request: HttpRequest) -> JsonResponse:
- skill_levels: A list of the Skill Levels
"""
return JsonResponse(export_framework(), json_dumps_params=dict(indent=2))
+
+
+class DownloadUserSkillDataCsvView(View):
+ """A view that returns the user skill data as a CSV file."""
+
+ def get(self, request: HttpRequest) -> HttpResponse:
+ """Define the GET response.
+
+ Args:
+ request: A GET request from the user.
+
+ Returns:
+ A HttpResponse containing the user skill data in CSV format.
+ """
+ user_skills = UserSkill.objects.filter(user=request.user.pk)
+ user_skills_data = [
+ {
+ "skill": user_skill.skill.name,
+ "competency_domain": user_skill.skill.competency.competency_domain.name,
+ "competency": user_skill.skill.competency.name,
+ "skill_level": user_skill.skill_level.level,
+ }
+ for user_skill in user_skills
+ ]
+ context = {}
+ context["chart_data"] = [
+ {
+ "user_id": "root",
+ "user_data": user_skills_data,
+ }
+ ]
+
+ response = HttpResponse(
+ content_type="text/csv",
+ headers={"Content-Disposition": 'attachment; filename="downloaded_.csv"'},
+ )
+ writer = csv.writer(response)
+ writer.writerow(["skill", "competency_domain", "competency", "skill_level"])
+ for user_skill in user_skills_data:
+ writer.writerow(
+ [
+ user_skill["skill"],
+ user_skill["competency_domain"],
+ user_skill["competency"],
+ user_skill["skill_level"],
+ ]
+ )
+ return response
+
+
+class DownloadUserSkillDataJSONView(View):
+ """A view that returns the user skill data as a JSON file."""
+
+ def get(self, request: HttpRequest) -> JsonResponse:
+ """Define the GET response.
+
+ Args:
+ request: A GET request from the user.
+
+ Returns:
+ A JsonResponse containing the user skill data in JSON format.
+ """
+ user_skills = UserSkill.objects.filter(user=request.user.pk)
+ # get_anonymous url param
+ get_anonymous = request.GET.get("anonymous", "false").lower() == "true"
+ user_skills_data = [
+ {
+ "skill": user_skill.skill.name,
+ "competency_domain": user_skill.skill.competency.competency_domain.name,
+ "competency": user_skill.skill.competency.name,
+ "skill_level": user_skill.skill_level.level,
+ }
+ for user_skill in user_skills
+ ]
+ returned_data = {
+ "username": "anonymous" if get_anonymous else request.user.username,
+ "user_data": user_skills_data,
+ }
+ return JsonResponse(returned_data, json_dumps_params=dict(indent=2))
diff --git a/tests/main/conftest.py b/tests/main/conftest.py
index 393eeac6..44d9a545 100644
--- a/tests/main/conftest.py
+++ b/tests/main/conftest.py
@@ -103,3 +103,10 @@ def skill_level() -> SkillLevel:
def user_skill(user, skill: Skill, skill_level: SkillLevel) -> UserSkill:
"""Fixture for creating a UserSkill instance."""
return UserSkill.objects.create(user=user, skill=skill, skill_level=skill_level)
+
+
+@pytest.fixture
+def client_logged_in(client, user):
+ """Fixture for a client that is logged in as a user."""
+ client.force_login(user)
+ return client
diff --git a/tests/main/test_data_views.py b/tests/main/test_data_views.py
index d4823396..6f101228 100644
--- a/tests/main/test_data_views.py
+++ b/tests/main/test_data_views.py
@@ -39,3 +39,81 @@ def test_framework_view_can_only_get(self, client):
assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED
response = client.trace(self._get_url())
assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED
+
+
+class TestDownloadCSVDataLinkView:
+ """Test suite for the DownloadCSVDataLinkView."""
+
+ def _get_url(self):
+ return reverse("download-csv-data-link")
+
+ def test_download_csv_data_link_view_get(self, client_logged_in):
+ """Test the GET returns the correct CSV data link."""
+ response = client_logged_in.get(self._get_url())
+ assert response.status_code == HTTPStatus.OK
+
+ def test_download_csv_data_link_view_returns_a_csv_file(self, client_logged_in):
+ """Test that the GET request returns a CSV file."""
+ response = client_logged_in.get(self._get_url())
+ assert response.status_code == HTTPStatus.OK
+ assert response["Content-Type"] == "text/csv"
+
+ def test_download_csv_data_link_view_csv_content(
+ self, client_logged_in, user_skill
+ ):
+ """Test that the CSV content is correct."""
+ response = client_logged_in.get(self._get_url())
+ assert response.status_code == HTTPStatus.OK
+ content = response.content.decode("utf-8")
+ assert content.startswith("skill")
+ assert "competency_domain" in content
+ assert "competency" in content
+ assert "skill_level" in content
+ assert user_skill.skill.name in content
+ assert (
+ content.count("\n") > 1
+ ) # Ensure there is at least one row of data besides the header
+
+
+class TestDownloadUserSkillDataJSONView:
+ """Test suite for the DownloadUserSkillDataJSONView."""
+
+ def _get_url(self):
+ return reverse("download-json-data-link")
+
+ def test_download_json_data_link_view_get(self, client_logged_in):
+ """Test the GET returns the correct JSON data link."""
+ response = client_logged_in.get(self._get_url())
+ assert response.status_code == HTTPStatus.OK
+
+ def test_download_json_data_link_view_returns_a_json_file(self, client_logged_in):
+ """Test that the GET request returns a JSON file."""
+ response = client_logged_in.get(self._get_url())
+ assert response.status_code == HTTPStatus.OK
+ assert response["Content-Type"] == "application/json"
+
+ def test_download_json_data_link_view_json_content(self, client_logged_in):
+ """Test that the JSON content is correct."""
+ response = client_logged_in.get(self._get_url())
+ assert response.status_code == HTTPStatus.OK
+ content = response.json()
+ assert isinstance(content, dict)
+ assert "username" in content
+ assert "user_data" in content
+ assert isinstance(content["user_data"], list)
+ if content["user_data"]:
+ assert "skill" in content["user_data"][0]
+ assert "competency_domain" in content["user_data"][0]
+ assert "competency" in content["user_data"][0]
+ assert "skill_level" in content["user_data"][0]
+
+ def test_returns_anonymous_username_when_anonymous_param_is_true(
+ self, client_logged_in
+ ):
+ """Assert username is 'anonymous' when anonymous URL para is set to true."""
+ response = client_logged_in.get(self._get_url() + "?anonymous=true")
+ assert response.status_code == HTTPStatus.OK
+ content = response.json()
+ assert content["username"] == "anonymous"
+ # Also check that the user_data is still present
+ assert "user_data" in content