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

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