diff --git a/main/admin.py b/main/admin.py
index 674949a6..a49f64af 100644
--- a/main/admin.py
+++ b/main/admin.py
@@ -3,12 +3,36 @@
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
-from .models import Category, Skill, SkillLevel, User, UserSkill
+from .models import (
+ Category,
+ ExampleUser,
+ ExampleUserSkill,
+ Skill,
+ SkillLevel,
+ User,
+ UserSkill,
+)
admin.site.register(User, UserAdmin)
-@admin.register(Category)
+@admin.register(ExampleUser)
+class ExampleUserAdmin(admin.ModelAdmin[ExampleUser]):
+ """Admin class for the ExampleUser model."""
+
+ list_display = ("name",)
+ search_fields = ("name",)
+ ordering = ("name",)
+
+
+@admin.register(ExampleUserSkill)
+class ExampleUserSkillAdmin(admin.ModelAdmin[ExampleUserSkill]):
+ """Admin class for the ExampleUserSkill model."""
+
+ list_display = ("example_user", "skill", "skill_level")
+ search_fields = ("example_user", "skill")
+
+
class CategoryAdmin(admin.ModelAdmin[Category]):
"""Admin class for the Category model."""
diff --git a/main/migrations/0007_exampleuser_exampleuserskill.py b/main/migrations/0007_exampleuser_exampleuserskill.py
new file mode 100644
index 00000000..b8e3f69a
--- /dev/null
+++ b/main/migrations/0007_exampleuser_exampleuserskill.py
@@ -0,0 +1,30 @@
+# Generated by Django 5.2.4 on 2025-08-25 21:03
+
+import django.db.models.deletion
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('main', '0006_skilllevel_description_skilllevel_level'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ExampleUser',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('name', models.CharField(max_length=100)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ExampleUserSkill',
+ fields=[
+ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('example_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.exampleuser')),
+ ('skill', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.skill')),
+ ('skill_level', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='main.skilllevel')),
+ ],
+ ),
+ ]
diff --git a/main/models.py b/main/models.py
index acea1018..47d8a3ef 100644
--- a/main/models.py
+++ b/main/models.py
@@ -11,6 +11,12 @@ class User(AbstractUser):
"""Custom user model for this project."""
+class ExampleUser(models.Model):
+ """Model for example users."""
+
+ name = models.CharField(max_length=100)
+
+
class Category(models.Model):
"""Model for categories."""
@@ -81,3 +87,11 @@ class UserSkill(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
skill = models.ForeignKey(Skill, on_delete=models.CASCADE)
skill_level = models.ForeignKey(SkillLevel, on_delete=models.CASCADE)
+
+
+class ExampleUserSkill(models.Model):
+ """Model for mapping example users to skills and skill levels."""
+
+ example_user = models.ForeignKey(ExampleUser, on_delete=models.CASCADE)
+ skill = models.ForeignKey(Skill, on_delete=models.CASCADE)
+ skill_level = models.ForeignKey(SkillLevel, on_delete=models.CASCADE)
diff --git a/main/templates/main/example_skill_profile.html b/main/templates/main/example_skill_profile.html
new file mode 100644
index 00000000..427c380f
--- /dev/null
+++ b/main/templates/main/example_skill_profile.html
@@ -0,0 +1,67 @@
+{% extends "main/base.html" %}
+{% load static %}
+{% load django_bootstrap5 %}
+{% block title %}
+ Digital Research Competencies Framework
+{% endblock title %}
+{% block content %}
+
+
Example Skill Profiles
+
+
+ {% for user_example in users_data_raw %}
+ - {{ user_example.name }}
+
+ {% endfor %}
+
+
+{% endblock content %}
diff --git a/main/urls.py b/main/urls.py
index 4891fdf5..0a15dede 100644
--- a/main/urls.py
+++ b/main/urls.py
@@ -15,4 +15,9 @@
path("contact/", views.ContactPageView.as_view(), name="contact"),
path("self-assess/", views.SelfAssessPageView.as_view(), name="self-assess"),
path("skills/", views.skill_profile, name="skill_profile"),
+ path(
+ "example/",
+ views.ExampleSkillProfileView.as_view(),
+ name="example_skill_profile",
+ ),
]
diff --git a/main/views.py b/main/views.py
index 7806d7f7..d314b477 100644
--- a/main/views.py
+++ b/main/views.py
@@ -2,7 +2,7 @@
import logging
from json import dumps
-from typing import TYPE_CHECKING, cast
+from typing import TYPE_CHECKING, Any, cast
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin
@@ -13,7 +13,7 @@
from django.views.generic.base import TemplateView
from django.views.generic.edit import UpdateView
-from .models import SkillLevel, UserSkill
+from .models import ExampleUser, ExampleUserSkill, SkillLevel, UserSkill
logger = logging.getLogger(__name__)
@@ -113,3 +113,50 @@ class SelfAssessPageView(TemplateView):
"""View that renders the self-assessment questionnaire page."""
template_name = "main/self-assess.html"
+
+
+class ExampleSkillProfileView(TemplateView):
+ """View that renders the example skill profile page."""
+
+ model = ExampleUser
+ fields = ("name",)
+ template_name = "main/example_skill_profile.html"
+
+ def get_context_data(self, **kwargs: Any) -> dict[str, Any]: # type: ignore
+ """Override the get context data to get example user and skill data."""
+ context = super().get_context_data(**kwargs)
+ example_users = ExampleUser.objects.all()
+ example_users_data = [
+ {
+ "name": example_user.name,
+ "id": example_user.id,
+ "skills": [
+ {
+ "name": example_skill.skill.name,
+ "category": example_skill.skill.category.name,
+ "level": example_skill.skill_level.level,
+ }
+ for example_skill in ExampleUserSkill.objects.filter(
+ example_user=example_user
+ )
+ ],
+ }
+ for example_user in example_users
+ ]
+
+ skill_levels = SkillLevel.objects.all()
+ skill_levels_data = [
+ {
+ "level": skill_level.level,
+ "name": skill_level.name,
+ "description": skill_level.description,
+ }
+ for skill_level in skill_levels
+ ]
+ # TODO: Reduce repetition of data here.
+ context = {
+ "users_data_raw": example_users,
+ "users_data": dumps(example_users_data),
+ "skill_levels": dumps(skill_levels_data),
+ }
+ return context
diff --git a/tests/main/test_main_views.py b/tests/main/test_main_views.py
index f75ce25a..f9077633 100644
--- a/tests/main/test_main_views.py
+++ b/tests/main/test_main_views.py
@@ -114,3 +114,29 @@ def test_provides_required_context(self, admin_client):
assert "skill_levels" in response.context
assert isinstance(response.context["skill_levels"], str)
# TODO: Improve this test
+
+
+class TestExampleSkillProfileView(TemplateOkMixin):
+ """Test suite for the ExampleSkillProfileView."""
+
+ _template_name = "main/example_skill_profile.html"
+
+ def _get_url(self):
+ return reverse("example_skill_profile")
+
+ def test_provides_required_context(self, admin_client):
+ """Test that the skill profile view renders the data visualization."""
+ response = admin_client.get(self._get_url())
+ assert response.status_code == 200
+ assert "user_data" in response.context
+ assert isinstance(response.context["users_data"], str)
+ assert len(response.context["users_data"]) > 0
+ assert "skill_levels" in response.context
+ assert isinstance(response.context["skill_levels"], str)
+ assert len(response.context["skill_levels"]) > 0
+
+ def test_shows_a_radial_plot_for_each_example_profile(self, admin_client):
+ """Test that a radial plot is shown for each example profile."""
+ response = admin_client.get(self._get_url())
+ assert response.status_code == 200
+ raise AssertionError("Test not implemented")