Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ repos:
additional_dependencies:
- django-stubs[compatible-mypy]
- django-stubs-ext
- types-Markdown
- types-requests
- django-bootstrap5
- django-registration
- django-crispy-forms
Expand Down
20 changes: 20 additions & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ beautifulsoup4==4.14.3
# via direct_webapp (pyproject.toml)
build==1.4.2
# via pip-tools
certifi==2026.4.22
# via requests
cfgv==3.5.0
# via pre-commit
charset-normalizer==3.4.7
# via requests
click==8.3.1
# via
# djlint
Expand Down Expand Up @@ -78,6 +82,8 @@ gunicorn==25.3.0
# via direct_webapp (pyproject.toml)
identify==2.6.18
# via pre-commit
idna==3.13
# via requests
iniconfig==2.3.0
# via pytest
jsbeautifier==1.15.4
Expand All @@ -88,6 +94,8 @@ json5==0.14.0
# via djlint
librt==0.8.1
# via mypy
markdown==3.10.2
# via direct_webapp (pyproject.toml)
mypy==1.20.0
# via
# direct_webapp (pyproject.toml)
Expand All @@ -96,6 +104,8 @@ 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
# via pre-commit
packaging==26.0
Expand Down Expand Up @@ -146,6 +156,8 @@ pyyaml==6.0.3
# pre-commit
regex==2026.3.32
# via djlint
requests==2.33.1
# via direct_webapp (pyproject.toml)
ruff==0.15.8
# via direct_webapp (pyproject.toml)
six==1.17.0
Expand All @@ -162,14 +174,22 @@ tqdm==4.67.3
# via djlint
types-django-import-export==4.4.0.20260325
# via direct_webapp (pyproject.toml)
types-markdown==3.10.2.20260408
# via direct_webapp (pyproject.toml)
types-pyyaml==6.0.12.20250915
# via django-stubs
types-requests==2.33.0.20260408
# via direct_webapp (pyproject.toml)
typing-extensions==4.15.0
# via
# beautifulsoup4
# django-stubs
# django-stubs-ext
# mypy
urllib3==2.6.3
# via
# requests
# types-requests
virtualenv==21.2.0
# via pre-commit
wheel==0.46.3
Expand Down
13 changes: 9 additions & 4 deletions doc-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ babel==2.18.0
# via mkdocs-material
backrefs==6.2
# via mkdocs-material
certifi==2026.2.25
certifi==2026.4.22
# via requests
charset-normalizer==3.4.6
charset-normalizer==3.4.7
# via requests
click==8.3.1
# via
Expand Down Expand Up @@ -61,7 +61,7 @@ griffelib==2.0.2
# via mkdocstrings-python
gunicorn==25.3.0
# via direct_webapp (pyproject.toml)
idna==3.11
idna==3.13
# via requests
jinja2==3.1.6
# via
Expand All @@ -71,6 +71,7 @@ jinja2==3.1.6
# properdocs
markdown==3.10.2
# via
# direct_webapp (pyproject.toml)
# mkdocs
# mkdocs-autorefs
# mkdocs-material
Expand Down Expand Up @@ -121,6 +122,8 @@ 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
# via
# gunicorn
Expand Down Expand Up @@ -161,7 +164,9 @@ pyyaml-env-tag==1.1
# mkdocs
# properdocs
requests==2.33.1
# via mkdocs-material
# via
# direct_webapp (pyproject.toml)
# mkdocs-material
six==1.17.0
# via python-dateutil
sqlparse==0.5.5
Expand Down
17 changes: 17 additions & 0 deletions main/templates/main/pages/policies/governance.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% extends "main/base.page.html" %}
{% load static %}
{% block title %}
Digital Research Competencies Framework
{% endblock title %}
{% block breadcrumb_items %}
<li class="breadcrumb-item">Policies</li>
<li class="breadcrumb-item active" aria-current="page">Governance</li>
{% endblock breadcrumb_items %}
{% block content %}
<section id="licensing" class="container">
<h1 class="display-4 text-center pb-2 pb-lg-3">{{ page_heading }}</h1>
<div class="row justify-content-center pt-xxl-2">
<div class="col-lg-9 col-xl-8">{{ markdown_content }}</div>
</div>
</section>
{% endblock content %}
17 changes: 17 additions & 0 deletions main/templates/main/pages/policies/licensing.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{% extends "main/base.page.html" %}
{% load static %}
{% block title %}
Digital Research Competencies Framework
{% endblock title %}
{% block breadcrumb_items %}
<li class="breadcrumb-item">Policies</li>
<li class="breadcrumb-item active" aria-current="page">Licensing</li>
{% endblock breadcrumb_items %}
{% block content %}
<section id="licensing" class="container">
<h1 class="display-4 text-center pb-2 pb-lg-3">{{ page_heading }}</h1>
<div class="row justify-content-center pt-xxl-2">
<div class="col-lg-9 col-xl-8">{{ markdown_content }}</div>
</div>
</section>
{% endblock content %}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
Digital Research Competencies Framework
{% endblock title %}
{% block breadcrumb_items %}
<li class="breadcrumb-item active" aria-current="page">Privacy Policy</li>
<li class="breadcrumb-item">Policies</li>
<li class="breadcrumb-item active" aria-current="page">Privacy</li>
{% endblock breadcrumb_items %}
{% block content %}
<section id="privacy" class="container">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
Digital Research Competencies Framework
{% endblock title %}
{% block breadcrumb_items %}
<li class="breadcrumb-item">Policies</li>
<li class="breadcrumb-item active" aria-current="page">Terms and Conditions</li>
{% endblock breadcrumb_items %}
{% block content %}
<section id="terms">
<section id="terms" class="container">
<h1 class="display-4 text-center pb-2 pb-lg-3">Terms and Conditions</h1>
</section>
<section class="container pt-5 mt-md-2 mt-lg-3 mt-xl-4">
<div class="row justify-content-center pt-xxl-2">
<div class="col-lg-9 col-xl-8">
<p class="fs-lg">
Expand Down
7 changes: 5 additions & 2 deletions main/templates/main/snippets/footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ <h6 class="text-muted text-uppercase fs-xs fw-semibold mb-3">About</h6>
<div class="col">
<h6 class="text-muted text-uppercase fs-xs fw-semibold mb-3">Framework</h6>
<ul class="nav flex-column mb-0">
<li class="nav-item mb-2">
<a class="nav-link p-0" href="{% url 'framework_overview' %}">Overview</a>
</li>
<li class="nav-item mb-2">
<a class="nav-link p-0" href="{% url 'skills_and_competencies' %}">Competencies framework</a>
</li>
Expand All @@ -107,10 +110,10 @@ <h6 class="text-muted text-uppercase fs-xs fw-semibold mb-3">Framework</h6>
<h6 class="text-muted text-uppercase fs-xs fw-semibold mb-3">Policies</h6>
<ul class="nav flex-column mb-0">
<li class="nav-item mb-2">
<a class="nav-link p-0" href="">Licensing</a>
<a class="nav-link p-0" href="{% url 'licensing' %}">Licensing</a>
</li>
<li class="nav-item mb-2">
<a class="nav-link p-0" href="">Goverenance</a>
<a class="nav-link p-0" href="{% url 'governance' %}">Governance</a>
</li>
<li class="nav-item mb-2">
<a class="nav-link p-0" href="{% url 'privacy' %}">Privacy policy</a>
Expand Down
2 changes: 1 addition & 1 deletion main/templates/main/snippets/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ <h6 class="dropdown-header fs-xs fw-medium text-body-secondary text-uppercase pb
<a class="dropdown-item" href="{% url 'learning_resources' %}">Learning resources</a>
</li>
<li>
<a class="dropdown-item" href="{% url 'roles' %}">Roles and job profiles</a>
<a class="dropdown-item" href="{% url 'roles' %}">Roles & career pathways</a>
</li>
</ul>
</li>
Expand Down
10 changes: 8 additions & 2 deletions main/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,15 @@
path("overview/", views.AccountOverviewView.as_view(), name="account-overview"),
]

policies_patterns = [
path("privacy/", views.PrivacyPageView.as_view(), name="privacy"),
path("terms/", views.TermsPageView.as_view(), name="terms"),
path("governance/", views.GovernancePageView.as_view(), name="governance"),
path("licensing/", views.LicensingPageView.as_view(), name="licensing"),
]

urlpatterns = [
path("", views.IndexPageView.as_view(), name="index"),
path("privacy/", views.PrivacyPageView.as_view(), name="privacy"),
path(
"accounts/register/",
RegistrationView.as_view(form_class=RegistrationForm),
Expand All @@ -45,13 +51,13 @@
path("accounts/", include("django.contrib.auth.urls")),
path("accounts/", include(accounts_patterns)),
path("about/", views.AboutPageView.as_view(), name="about"),
path("terms/", views.TermsPageView.as_view(), name="terms"),
path(
"terms-acceptance/",
views.TermsAcceptanceView.as_view(),
name="terms_acceptance",
),
path("framework/", include(framework_patterns)),
path("policies/", include(policies_patterns)),
path("get-involved/", views.GetInvolvedPageView.as_view(), name="get_involved"),
path("events/", views.EventsPageView.as_view(), name="events"),
path("framework-json/", views.FrameworkView.as_view(), name="framework_json"),
Expand Down
94 changes: 91 additions & 3 deletions main/views/page_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
from collections.abc import Mapping
from json import dumps
from pathlib import Path
from typing import Any
from typing import Any, ClassVar

import markdown
import nh3
import requests
from django.shortcuts import get_object_or_404
from django.templatetags.static import static
from django.utils.decorators import method_decorator
from django.utils.safestring import mark_safe
from django.views.decorators.cache import cache_page
from django.views.generic.base import TemplateView
from django_tables2 import SingleTableView

Expand Down Expand Up @@ -65,7 +71,7 @@ def get_context_data(self, **kwargs: Mapping[str, Any]) -> dict[str, Any]:
class PrivacyPageView(TemplateView):
"""View that renders the privacy page."""

template_name = "main/pages/privacy.html"
template_name = "main/pages/policies/privacy.html"


class AboutPageView(TemplateView):
Expand All @@ -77,7 +83,7 @@ class AboutPageView(TemplateView):
class TermsPageView(TemplateView):
"""View that renders the terms and conditions page."""

template_name = "main/pages/terms.html"
template_name = "main/pages/policies/terms.html"


class SkillLevelsPageView(TemplateView):
Expand Down Expand Up @@ -222,3 +228,85 @@ class FrameworkOverviewPageView(TemplateView):
"""View that renders an overview page for the framework."""

template_name = "main/pages/framework-overview.html"


class GitHubMarkdownPageView(TemplateView):
"""Base view for pages that render markdown content fetched from GitHub."""

github_raw_url = ""
page_heading = ""
unavailable_message = "Document is temporarily unavailable."
markdown_extensions: ClassVar[list[str]] = ["fenced_code", "tables", "toc"]

def _strip_duplicate_heading(self, markdown_text: str) -> str:
"""Remove leading H1 if it duplicates the configured page heading."""
lines = markdown_text.splitlines()
if not lines:
return markdown_text

first_line = lines[0].strip()
if self.page_heading not in first_line:
return markdown_text

# Remove the duplicate title line and one following blank line if present.
del lines[0]
if lines and lines[0].strip() == "":
del lines[0]

return "\n".join(lines)

def get_markdown_content(self) -> str:
"""Fetch and convert remote markdown content to HTML."""
if not self.github_raw_url:
raise ValueError("github_raw_url must be set on GitHubMarkdownPageView")

response = requests.get(self.github_raw_url, timeout=5)
response.raise_for_status()
markdown_text = self._strip_duplicate_heading(response.text)
return markdown.markdown(
markdown_text,
extensions=self.markdown_extensions,
)
Comment thread
davehorsfall marked this conversation as resolved.

def get_context_data(self, **kwargs: Mapping[str, Any]) -> dict[str, Any]:
"""Add rendered markdown content and page metadata to the context."""
context = super().get_context_data(**kwargs)
context["page_heading"] = self.page_heading
Comment thread
davehorsfall marked this conversation as resolved.

try:
context["markdown_content"] = mark_safe(
nh3.clean(self.get_markdown_content())
)
except requests.RequestException:
logger.exception("Failed to load markdown from %s", self.github_raw_url)
context["markdown_content"] = mark_safe(
f"<p>{self.unavailable_message}</p>"
)

return context


@method_decorator(cache_page(60 * 60), name="dispatch") # cache 1 hour
class GovernancePageView(GitHubMarkdownPageView):
"""View that renders the governance page from GitHub Markdown."""

template_name = "main/pages/policies/governance.html"
page_heading = "DIRECT Governance"
unavailable_message = "Governance document is temporarily unavailable."

github_raw_url = (
"https://raw.githubusercontent.com/direct-framework/.github/main/GOVERNANCE.md"
)


@method_decorator(cache_page(60 * 60), name="dispatch") # cache 1 hour
class LicensingPageView(GitHubMarkdownPageView):
"""View that renders the licensing page from GitHub Markdown."""

template_name = "main/pages/policies/licensing.html"
page_heading = "DIRECT Licensing"
unavailable_message = "Licensing document is temporarily unavailable."

github_raw_url = (
"https://raw.githubusercontent.com/direct-framework/.github/main/LICENSING.md"
)
Comment on lines +290 to +312
Copy link

Copilot AI Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New Governance/Licensing views introduce network-fetch + markdown rendering behavior but there are no tests covering these routes (template used, status code, and the failure path when the GitHub fetch errors). Add view tests that mock the HTTP request (e.g., via requests-mock/pytest-mock) to keep the test suite deterministic.

Copilot uses AI. Check for mistakes.
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ dependencies = [
"mysqlclient",
"django-import-export",
"django-multiselectfield",
"django-tables2"
"django-tables2",
"markdown",
"nh3",
"requests"
]

[project.optional-dependencies]
Expand All @@ -34,6 +37,8 @@ dev = [
"django-stubs[compatible-mypy]",
"djlint",
"types-django-import-export",
"types-Markdown",
"types-requests",
Comment thread
AdrianDAlessandro marked this conversation as resolved.
"beautifulsoup4",
]
Comment thread
AdrianDAlessandro marked this conversation as resolved.
doc = [
Expand Down
Loading
Loading