Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions main/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down
30 changes: 30 additions & 0 deletions main/migrations/0007_exampleuser_exampleuserskill.py
Original file line number Diff line number Diff line change
@@ -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')),
],
),
]
14 changes: 14 additions & 0 deletions main/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down Expand Up @@ -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)
67 changes: 67 additions & 0 deletions main/templates/main/example_skill_profile.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{% extends "main/base.html" %}
{% load static %}
{% load django_bootstrap5 %}
{% block title %}
Digital Research Competencies Framework
{% endblock title %}
{% block content %}
<!-- Features grid -->
<h1>Example Skill Profiles</h1>
<section class="container-fluid pt-5 mt-lg-3 mt-xl-4 mt-xxl-5">
<div class="bg-body rounded-5 d-flex justify-content-center align-items-center"
id="dataviz_root"
style="width:100%"></div>
</section>
<ul>
{% for user_example in users_data_raw %}
<li>{{ user_example.name }}</li>
<section class="container-fluid pt-5 mt-lg-3 mt-xl-4 mt-xxl-5">
<!-- A placeholder div for each radial plot -->
<div class="bg-body rounded-5 d-flex justify-content-center align-items-center"
id="dataviz_root_{{ user_example.id }}"
style="width:100%"></div>
</section>
{% endfor %}
</ul>
<script>
document.addEventListener('DOMContentLoaded', function () {
const skillLevelsLoadedFromContext = {{ skill_levels|safe }};
const usersData = {{ users_data|safe }};
console.info("Data loaded from context:", usersData);
usersData.forEach(user => {
console.info("Rendering user:", user);
const datavizRoot = document.getElementById('dataviz_root_' + user.id);
const radialBarChartConfig = {
innerRadius: 0,
width: 900,
// height: _height:undefined,
innerRadius: 200,
outerPadding: 80,
categoryPadding: 0.1,
skillPadding: 0.05,
arcPercent: 0.8,
arcStartOffset: 0.1,
annotationPadding: 10,
lineThickness: 2,
labelTextColor: "white",
lvlTextColor: "var(--ar-primary)",
lvlArcColor: "rgba(var(--ar-primary-rgb), 0.2)",
colourList: [
"var(--ar-warning-text-emphasis)",
"var(--ar-primary)",
"var(--ar-success-text-emphasis)",
"var(--ar-info-text-emphasis)",
],
}
const userDataLoadedFromContext = user;
main().RadialBarChart({
target: datavizRoot,
data: userDataLoadedFromContext.skills,
levels: skillLevelsLoadedFromContext,
config: radialBarChartConfig,
});
});
});

</script>
{% endblock content %}
5 changes: 5 additions & 0 deletions main/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
),
]
51 changes: 49 additions & 2 deletions main/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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__)

Expand Down Expand Up @@ -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
26 changes: 26 additions & 0 deletions tests/main/test_main_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Loading