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
80 changes: 57 additions & 23 deletions main/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
'<a href="{}" target="_blank" rel="noopener noreferrer" class="{}">{}</a>'
)
badge_html = '<span class="badge bg-secondary fs-sm">{}</span>'

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",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

accessibility: this is a link, not a button (it doesn't do something on the page, it takes you somewhere else) so it shouldn't really be styled like a button

skill.name,
)
for skill in qs
)
)


class LearningResourceTable(tables.Table):
"""Table class for the LearningResources model."""

skill_set = tables.ManyToManyColumn(verbose_name="Skills")
Expand All @@ -27,33 +48,46 @@ class Meta:

def render_name(self, value: str, record: LearningResource) -> SafeString:
"""Include the URL in the name."""
return format_html(
'<a href="{}" target="_blank" rel="noopener noreferrer">{}</a>',
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:
"""Include the URL in the provider name."""
if record.provider is None or not record.provider.url:
return mark_safe(value)

return format_html(
'<a href="{}" target="_blank" rel="noopener noreferrer">{}</a>',
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(
'<a href="{}" target="_blank" rel="noopener noreferrer" '
'class="badge bg-secondary">{}</a>',
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: ToolLanguageMethodology) -> 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())
22 changes: 22 additions & 0 deletions main/templates/main/pages/tools-languages-methodologies.html
Original file line number Diff line number Diff line change
@@ -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 %}
<li class="breadcrumb-item">
<a href="{% url 'framework_overview' %}">Framework</a>
</li>
<li class="breadcrumb-item active" aria-current="page">Tools, languages and methodologies</li>
{% endblock breadcrumb_items %}
{% block content %}
<section id="learning-resources">
<div class="row">
<div class="col pe-lg-4 pe-xl-0">
<h1 class="pb-2 pb-lg-3">Tools, languages and methodologies</h1>
{% render_table table %}
</div>
</div>
</section>
{% endblock content %}
6 changes: 5 additions & 1 deletion main/templates/main/snippets/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,11 @@ <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 & career pathways</a>
<a class="dropdown-item"
href="{% url 'tools_languages_methodologies' %}">Tools, languages and methodologies</a>
</li>
<li>
<a class="dropdown-item" href="{% url 'roles' %}">Roles and career pathways</a>
</li>
</ul>
</li>
Expand Down
5 changes: 5 additions & 0 deletions main/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
views.LearningResourcesPageView.as_view(),
name="learning_resources",
),
path(
"tools-languages-methodologies/",
views.ToolsLanguagesMethodologiesPageView.as_view(),
name="tools_languages_methodologies",
),
path("roles/", views.RolesPageView.as_view(), name="roles"),
path("skills/<slug:slug>/", views.SkillPageView.as_view(), name="skill_detail"),
]
Expand Down
12 changes: 10 additions & 2 deletions main/views/page_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
SkillLevel,
ToolLanguageMethodology,
)
from ..tables import LearningResourcesTable
from ..tables import LearningResourceTable, ToolLanguageMethodologyTable

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -103,10 +103,18 @@ class LearningResourcesPageView(SingleTableView):
"""View that renders the page with all learning resources."""

model = LearningResource
table_class = LearningResourcesTable
table_class = LearningResourceTable
template_name = "main/pages/learning-resources.html"


class ToolsLanguagesMethodologiesPageView(SingleTableView):
"""View that renders the page with all tools, languages and methodologies."""

model = ToolLanguageMethodology
table_class = ToolLanguageMethodologyTable
template_name = "main/pages/tools-languages-methodologies.html"


class GetInvolvedPageView(TemplateView):
"""View that renders the get involved page."""

Expand Down
11 changes: 8 additions & 3 deletions tests/main/test_main_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,12 @@ def test_navbar_contents(self, soup, auth_soup, admin_soup):
href=reverse("learning_resources"),
)
assert framework_dropdown.find(
tag_with_text_filter("a", "Roles & career pathways"), href=reverse("roles")
tag_with_text_filter("a", "Tools, languages and methodologies"),
href=reverse("tools_languages_methodologies"),
)
assert framework_dropdown.find(
tag_with_text_filter("a", "Roles and career pathways"),
href=reverse("roles"),
)

# Community Dropdown
Expand Down Expand Up @@ -415,13 +420,13 @@ def test_page_content(self, learning_resource, skill, soup):
assert tr.find(
tag_with_text_filter("a", "Learning Resource"), href=learning_resource.url
)
assert tr.find(tag_with_text_filter("td", "English"))
assert tr.find(tag_with_text_filter("span", "English"), class_="badge")
assert tr.find(
tag_with_text_filter("a", "Provider"), href=learning_resource.provider.url
)
assert tr.find(
tag_with_text_filter("a", "Skill"),
class_="badge",
class_="btn",
href=reverse("skill_detail", args=(skill.slug,)),
)

Expand Down
60 changes: 51 additions & 9 deletions tests/main/test_tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,35 @@
import pytest
from django.urls import reverse

from main.models import LearningResource, Provider, Skill
from main.tables import LearningResourcesTable
from main.models import LearningResource, Provider, Skill, ToolLanguageMethodology
from main.tables import LearningResourceTable, ToolLanguageMethodologyTable


@pytest.mark.django_db
def test_learning_resources_table_render_name(learning_resource: LearningResource):
"""Test the learning resource name renders as an external link."""
table = LearningResourcesTable([])
table = LearningResourceTable([])

rendered = table.render_name(learning_resource.name, learning_resource)

assert str(rendered) == (
f'<a href="{learning_resource.url}" target="_blank" '
'rel="noopener noreferrer">Learning Resource</a>'
'rel="noopener noreferrer" class="fs-lg">Learning Resource</a>'
)


@pytest.mark.django_db
def test_learning_resources_table_render_provider(learning_resource: LearningResource):
"""Test the provider name renders as an external link when a URL exists."""
table = LearningResourcesTable([])
table = LearningResourceTable([])

assert isinstance(learning_resource.provider, Provider)

rendered = table.render_provider(learning_resource.provider.name, learning_resource)

assert str(rendered) == (
f'<a href="{learning_resource.provider.url}" target="_blank" '
'rel="noopener noreferrer">Provider</a>'
'rel="noopener noreferrer" class="">Provider</a>'
)


Expand All @@ -40,7 +40,7 @@ def test_learning_resources_table_render_provider_without_url(
learning_resource: LearningResource,
):
"""Test the provider name renders as plain text when no URL exists."""
table = LearningResourcesTable([])
table = LearningResourceTable([])
learning_resource.provider = None

rendered = table.render_provider("Provider", learning_resource)
Expand All @@ -53,11 +53,53 @@ def test_learning_resources_table_render_skill_set(
learning_resource: LearningResource, skill: Skill
):
"""Test related skills render as badge links."""
table = LearningResourcesTable([])
table = LearningResourceTable([])

rendered = table.render_skill_set(learning_resource.skill_set) # type: ignore[arg-type]

assert str(rendered) == (
'<a href="{}" target="_blank" rel="noopener noreferrer" '
'class="badge bg-secondary">Skill</a>'
'class="btn btn-outline-primary rounded-pill btn-sm">Skill</a>'
).format(reverse("skill_detail", args=(skill.slug,)))


@pytest.mark.django_db
def test_tool_language_methodology_table_render_name(
tool: ToolLanguageMethodology,
):
"""Test the tool name renders as an external link."""
table = ToolLanguageMethodologyTable([])

rendered = table.render_name(tool.name, tool)

assert str(rendered) == (
f'<a href="{tool.url}" target="_blank" '
'rel="noopener noreferrer" class="fs-lg">Tool</a>'
)


@pytest.mark.django_db
def test_tool_language_methodology_table_render_kind(
tool: ToolLanguageMethodology,
):
"""Test the tool kind renders as a badge."""
table = ToolLanguageMethodologyTable([])

rendered = table.render_kind(tool.kind)

assert str(rendered) == '<span class="badge bg-secondary fs-sm">tool</span>'


@pytest.mark.django_db
def test_tool_language_methodology_table_render_skill_set(
tool: ToolLanguageMethodology, skill: Skill
):
"""Test related skills render as badge links."""
table = ToolLanguageMethodologyTable([])

rendered = table.render_skill_set(tool.skill_set) # type: ignore[arg-type]

assert str(rendered) == (
'<a href="{}" target="_blank" rel="noopener noreferrer" '
'class="btn btn-outline-primary rounded-pill btn-sm">Skill</a>'
).format(reverse("skill_detail", args=(skill.slug,)))
Loading