From 7c0ac19c5ccf865e9de7982f2be37cde91ec3ded Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Thu, 30 Apr 2026 10:25:10 +0100 Subject: [PATCH 01/10] add python minimum version --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7f6e6a1a..2c61984c 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,8 @@ This Django project uses: ## Installation +You must have **Python version 3.12 or later** installed. + To get started: 1. Create and activate a [virtual environment](https://docs.python.org/3/library/venv.html): From 6d9a6d414acf4c46f823c63a5cdaad873fd2ca1a Mon Sep 17 00:00:00 2001 From: Kevin Rue-Albrecht Date: Thu, 30 Apr 2026 11:14:22 +0100 Subject: [PATCH 02/10] move mysqlclient out of core and into new prod requirements --- dev-requirements.txt | 4 +-- doc-requirements.txt | 4 +-- prod-requirements.txt | 69 +++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 4 ++- requirements.txt | 4 +-- 5 files changed, 75 insertions(+), 10 deletions(-) create mode 100644 prod-requirements.txt diff --git a/dev-requirements.txt b/dev-requirements.txt index 080ec9bf..7b2534ed 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.13 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile --extra=dev --output-file=dev-requirements.txt @@ -102,8 +102,6 @@ mypy==1.20.0 # django-stubs mypy-extensions==1.1.0 # via mypy -mysqlclient==2.2.8 - # via direct_webapp (pyproject.toml) nh3==0.3.4 # via direct_webapp (pyproject.toml) nodeenv==1.10.0 diff --git a/doc-requirements.txt b/doc-requirements.txt index 630147ab..e2fa5e0c 100644 --- a/doc-requirements.txt +++ b/doc-requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.13 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile --extra=doc --output-file=doc-requirements.txt @@ -120,8 +120,6 @@ mkdocstrings==1.0.3 # mkdocstrings-python mkdocstrings-python==2.0.3 # via direct_webapp (pyproject.toml) -mysqlclient==2.2.8 - # via direct_webapp (pyproject.toml) nh3==0.3.4 # via direct_webapp (pyproject.toml) packaging==26.0 diff --git a/prod-requirements.txt b/prod-requirements.txt new file mode 100644 index 00000000..e0055a91 --- /dev/null +++ b/prod-requirements.txt @@ -0,0 +1,69 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --extra=prod --output-file=prod-requirements.txt +# +asgiref==3.11.1 + # via django +certifi==2026.4.22 + # via requests +charset-normalizer==3.4.7 + # via requests +confusable-homoglyphs==3.3.1 + # via django-registration +crispy-bootstrap5==2026.3 + # via direct_webapp (pyproject.toml) +diff-match-patch==20241021 + # via django-import-export +django==6.0.3 + # via + # crispy-bootstrap5 + # direct_webapp (pyproject.toml) + # django-bootstrap5 + # django-crispy-forms + # django-import-export + # django-multiselectfield + # django-registration + # django-stubs-ext + # django-tables2 +django-bootstrap5==26.2 + # via direct_webapp (pyproject.toml) +django-crispy-forms==2.6 + # via + # crispy-bootstrap5 + # direct_webapp (pyproject.toml) +django-import-export==4.4.0 + # via direct_webapp (pyproject.toml) +django-multiselectfield==1.0.1 + # via direct_webapp (pyproject.toml) +django-registration==5.2.1 + # via direct_webapp (pyproject.toml) +django-stubs-ext==6.0.2 + # via direct_webapp (pyproject.toml) +django-tables2==3.0.0 + # via direct_webapp (pyproject.toml) +gunicorn==25.3.0 + # via direct_webapp (pyproject.toml) +idna==3.13 + # via requests +markdown==3.10.2 + # via direct_webapp (pyproject.toml) +mysqlclient==2.2.8 + # via direct_webapp (pyproject.toml) +nh3==0.3.4 + # via direct_webapp (pyproject.toml) +packaging==26.0 + # via gunicorn +requests==2.33.1 + # via direct_webapp (pyproject.toml) +sqlparse==0.5.5 + # via django +tablib==3.9.0 + # via django-import-export +typing-extensions==4.15.0 + # via django-stubs-ext +urllib3==2.6.3 + # via requests +whitenoise==6.12.0 + # via direct_webapp (pyproject.toml) diff --git a/pyproject.toml b/pyproject.toml index 17f76c63..27795725 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ dependencies = [ "crispy-bootstrap5", "gunicorn", "whitenoise", - "mysqlclient", "django-import-export", "django-multiselectfield", "django-tables2", @@ -41,6 +40,9 @@ dev = [ "types-requests", "beautifulsoup4", ] +prod = [ + "mysqlclient", +] doc = [ "mkdocs<2", "mkdocstrings", diff --git a/requirements.txt b/requirements.txt index 39dfe18a..4b553555 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.13 +# This file is autogenerated by pip-compile with Python 3.12 # by the following command: # # pip-compile @@ -49,8 +49,6 @@ idna==3.13 # via requests markdown==3.10.2 # via direct_webapp (pyproject.toml) -mysqlclient==2.2.8 - # via direct_webapp (pyproject.toml) nh3==0.3.4 # via direct_webapp (pyproject.toml) packaging==26.0 From bda77a960a00e68cdc9a6b5dc9e4b27fc1a1855c Mon Sep 17 00:00:00 2001 From: Kevin Rue-Albrecht Date: Thu, 30 Apr 2026 11:15:33 +0100 Subject: [PATCH 03/10] edit Dockerfile to use prod requirements --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 155e1366..256aa461 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,7 +14,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && rm -rf /var/lib/apt/lists/* COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt +RUN pip install --no-cache-dir -r prod-requirements.txt COPY --chown=nobody:nogroup . . From ea31bca342ea52aac38e73b93db30bac3f2fe82f Mon Sep 17 00:00:00 2001 From: Adrian D'Alessandro Date: Fri, 17 Apr 2026 16:42:07 +0100 Subject: [PATCH 04/10] Add the Tools, languages and methodologies page --- main/tables.py | 80 +++++++++++++------ .../pages/tools-languages-methodologies.html | 22 +++++ main/templates/main/snippets/navbar.html | 6 +- main/urls.py | 5 ++ main/views/page_views.py | 12 ++- tests/main/test_tables.py | 10 +-- 6 files changed, 104 insertions(+), 31 deletions(-) create mode 100644 main/templates/main/pages/tools-languages-methodologies.html diff --git a/main/tables.py b/main/tables.py index 940d5662..6fbec153 100644 --- a/main/tables.py +++ b/main/tables.py @@ -3,17 +3,38 @@ from typing import TYPE_CHECKING import django_tables2 as tables +from django.db.models.query import QuerySet from django.urls import reverse from django.utils.html import format_html from django.utils.safestring import SafeString, mark_safe -from .models import LearningResource, Skill +from .models import LearningResource, Skill, ToolLanguageMethodology if TYPE_CHECKING: # pragma: no cover from django.db.models.fields.related_descriptors import ManyRelatedManager +external_link_html = ( + '{}' +) +badge_html = '{}' -class LearningResourcesTable(tables.Table): + +def _render_skills(qs: QuerySet[Skill]) -> SafeString: + """Helper function for rendering skills links as buttons in tables.""" + return mark_safe( + " ".join( + format_html( + external_link_html, + reverse("skill_detail", args=(skill.slug,)), + "btn btn-outline-primary rounded-pill btn-sm", + skill.name, + ) + for skill in qs + ) + ) + + +class LearningResourceTable(tables.Table): """Table class for the LearningResources model.""" skill_set = tables.ManyToManyColumn(verbose_name="Skills") @@ -27,10 +48,12 @@ class Meta: def render_name(self, value: str, record: LearningResource) -> SafeString: """Include the URL in the name.""" - return format_html( - '{}', - record.url, - value, + return format_html(external_link_html, record.url, "fs-lg", value) + + def render_language(self, value: str) -> SafeString: + """Render the language field as a badge.""" + return mark_safe( + " ".join(format_html(badge_html, val) for val in value.split(",")) ) def render_provider(self, value: str, record: LearningResource) -> SafeString: @@ -38,22 +61,33 @@ def render_provider(self, value: str, record: LearningResource) -> SafeString: if record.provider is None or not record.provider.url: return mark_safe(value) - return format_html( - '{}', - record.provider.url, - value, - ) + return format_html(external_link_html, record.provider.url, "", value) def render_skill_set(self, value: "ManyRelatedManager[Skill]") -> SafeString: - """Include the relevant skills as badges.""" - return mark_safe( - "".join( - format_html( - '{}', - reverse("skill_detail", args=(skill.slug,)), - skill.name, - ) - for skill in value.all() - ) - ) + """Include the relevant skills as button links.""" + return _render_skills(value.all()) + + +class ToolLanguageMethodologyTable(tables.Table): + """Table class for the LearningResources model.""" + + skill_set = tables.ManyToManyColumn(verbose_name="Skills") + + class Meta: + """Meta options for the LearningResourcesTable.""" + + model = ToolLanguageMethodology + fields = ("name", "kind") + order_by = "name" + + def render_name(self, value: str, record: LearningResource) -> SafeString: + """Include the URL in the name.""" + return format_html(external_link_html, record.url, "fs-lg", value) + + def render_kind(self, value: str) -> SafeString: + """Render the kind field as a badge.""" + return format_html(badge_html, value) + + def render_skill_set(self, value: "ManyRelatedManager[Skill]") -> SafeString: + """Include the relevant skills as button links.""" + return _render_skills(value.all()) diff --git a/main/templates/main/pages/tools-languages-methodologies.html b/main/templates/main/pages/tools-languages-methodologies.html new file mode 100644 index 00000000..c83668e3 --- /dev/null +++ b/main/templates/main/pages/tools-languages-methodologies.html @@ -0,0 +1,22 @@ +{% extends "main/base.page.html" %} +{% load static %} +{% load render_table from django_tables2 %} +{% block title %} + Digital Research Competencies Framework +{% endblock title %} +{% block breadcrumb_items %} + + +{% endblock breadcrumb_items %} +{% block content %} +
+
+
+

Tools, languages and methodologies

+ {% render_table table %} +
+
+
+{% endblock content %} diff --git a/main/templates/main/snippets/navbar.html b/main/templates/main/snippets/navbar.html index ba313b63..491c8484 100644 --- a/main/templates/main/snippets/navbar.html +++ b/main/templates/main/snippets/navbar.html @@ -115,7 +115,11 @@