Skip to content
Open
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
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ repos:
- types-django-import-export
- django-multiselectfield
- django-tables2
- django-filter
- repo: https://github.com/igorshubovych/markdownlint-cli
rev: v0.50.0
hooks:
Expand Down
23 changes: 9 additions & 14 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --extra=dev --output-file=dev-requirements.txt
# pip-compile --extra=dev --no-index --output-file=dev-requirements.txt
#
asgiref==3.13.0
# via django
ast-serialize==0.13.1
# via mypy
beautifulsoup4==4.15.0
# via direct_webapp (pyproject.toml)
build==1.6.1
Expand All @@ -20,8 +22,6 @@ click==8.5.0
# via
# djlint
# pip-tools
colorama==0.4.6
# via djlint
confusable-homoglyphs==3.3.1
# via django-registration
coverage[toml]==7.17.0
Expand All @@ -40,6 +40,7 @@ django==6.1.2
# direct_webapp (pyproject.toml)
# django-bootstrap5
# django-crispy-forms
# django-filter
# django-import-export
# django-multiselectfield
# django-registration
Expand All @@ -52,6 +53,8 @@ django-crispy-forms==2.7
# via
# crispy-bootstrap5
# direct_webapp (pyproject.toml)
django-filter==26.2
# via direct_webapp (pyproject.toml)
django-import-export==4.4.4
# via direct_webapp (pyproject.toml)
django-multiselectfield==1.0.1
Expand Down Expand Up @@ -109,8 +112,8 @@ nodeenv==1.11.0
packaging==26.3
# via
# build
# gunicorn
# pytest
# virtualenv
# wheel
pathspec==1.1.1
# via
Expand All @@ -119,9 +122,7 @@ pathspec==1.1.1
pip-tools==7.6.2
# via direct_webapp (pyproject.toml)
platformdirs==4.14.0
# via
# python-discovery
# virtualenv
# via virtualenv
pluggy==1.7.0
# via
# pytest
Expand Down Expand Up @@ -158,18 +159,12 @@ requests==2.34.2
# via direct_webapp (pyproject.toml)
ruff==0.17.3
# via direct_webapp (pyproject.toml)
six==1.17.0
# via
# cssbeautifier
# jsbeautifier
soupsieve==3.0.4
# via beautifulsoup4
sqlparse==0.6.0
# via django
tablib==3.10.0
# via django-import-export
tqdm==4.70.1
# via djlint
types-django-import-export==4.4.0.20260813
# via direct_webapp (pyproject.toml)
types-markdown==3.11.2.20261029
Expand Down
1 change: 1 addition & 0 deletions direct_webapp/settings/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"crispy_bootstrap5",
"import_export",
"django_tables2",
"django_filters",
Comment thread
Sahil590 marked this conversation as resolved.
Resolved
]
MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware")
STATIC_ROOT = BASE_DIR / "staticfiles"
Expand Down
10 changes: 7 additions & 3 deletions doc-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --extra=doc --output-file=doc-requirements.txt
# pip-compile --extra=doc --no-index --output-file=doc-requirements.txt
#
asgiref==3.13.0
# via django
Expand Down Expand Up @@ -32,6 +32,7 @@ django==6.1.2
# direct_webapp (pyproject.toml)
# django-bootstrap5
# django-crispy-forms
# django-filter
# django-import-export
# django-multiselectfield
# django-registration
Expand All @@ -43,6 +44,8 @@ django-crispy-forms==2.7
# via
# crispy-bootstrap5
# direct_webapp (pyproject.toml)
django-filter==26.2
# via direct_webapp (pyproject.toml)
django-import-export==4.4.4
# via direct_webapp (pyproject.toml)
django-multiselectfield==1.0.1
Expand Down Expand Up @@ -124,7 +127,6 @@ nh3==0.3.7
# via direct_webapp (pyproject.toml)
packaging==26.3
# via
# gunicorn
# mkdocs
# properdocs
paginate==0.5.7
Expand Down Expand Up @@ -173,6 +175,8 @@ tablib==3.10.0
# via django-import-export
typing-extensions==4.16.0
# via django-stubs-ext
tzdata==2026.6
# via python-dateutil
urllib3==2.8.0
# via requests
watchdog==6.0.0
Expand Down
43 changes: 43 additions & 0 deletions main/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Filters for browsing learning resources."""

from django import forms
from django.db.models import Q, QuerySet
from django_filters import CharFilter, FilterSet

from .models import LearningResource


class LearningResourceFilter(FilterSet):
"""Search learning resources through a single cross-field search box."""

q = CharFilter(
method="filter_search",
label="Search learning resources",
widget=forms.SearchInput(
attrs={
"id": "resource-filter",
"placeholder": "Filter learning resources",
}
),
)

class Meta:
"""Limit filtering to the explicitly supported query parameters."""

model = LearningResource
fields = ("q",)

def filter_search(
self,
queryset: QuerySet[LearningResource],
name: str,
value: str,
) -> QuerySet[LearningResource]:
"""Search resource details and related names without duplicate rows."""
return queryset.filter(
Q(name__icontains=value)
| Q(description__icontains=value)
| Q(language__icontains=value)
| Q(provider__name__icontains=value)
| Q(skill__name__icontains=value)
).distinct()
18 changes: 8 additions & 10 deletions main/templates/main/pages/learning-resources.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% extends "main/base.page.html" %}
{% load static %}
{% load crispy_forms_tags %}
{% load render_table from django_tables2 %}
{% block title %}
Learning Resources
Expand All @@ -16,17 +17,14 @@
<div class="col pe-lg-4 pe-xl-0">
<h1 class="pb-2 pb-lg-3">Learning resources</h1>
<form method="get" class="d-flex justify-content-end mb-4">
<div class="input-group w-auto">
<label for="resource-filter"
class="visually-hidden">Search learning resources</label>
<input id="resource-filter"
name="q"
type="search"
value="{{ query }}"
class="form-control"
size="25"
placeholder="Filter learning resources">
{{ filter.form.q|as_crispy_field }}
{% if table.order_by %}
<input type="hidden" name="sort" value="{{ table.order_by }}">
{% endif %}
<div class="d-flex gap-2 justify-content-end">
<button type="submit" class="btn btn-primary">Search</button>
<a href="{% url 'learning_resources' %}"
class="btn btn-outline-secondary">Reset</a>
</div>
</form>
{% if table.rows %}
Expand Down
27 changes: 6 additions & 21 deletions main/views/page_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
from django.views.decorators.cache import cache_page
from django.views.generic import DetailView
from django.views.generic.base import TemplateView
from django_tables2 import RequestConfig, SingleTableView
from django_filters.views import FilterView
from django_tables2 import RequestConfig, SingleTableMixin, SingleTableView

from ..filters import LearningResourceFilter
from ..models import (
CompetencyDomain,
Event,
Expand Down Expand Up @@ -174,38 +176,21 @@ def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
return context


class LearningResourcesPageView(SingleTableView):
class LearningResourcesPageView(SingleTableMixin, FilterView):
"""View that renders a page with all learning resources.

Learning resources are listed in a table that is searchable and paginated.
"""

model = LearningResource
table_class = LearningResourceTable
filterset_class = LearningResourceFilter
template_name = "main/pages/learning-resources.html"
paginate_by = 20

def get_queryset(self) -> QuerySet[LearningResource]:
"""Return learning resources filtered by the optional search query."""
queryset = LearningResource.objects.select_related("provider").order_by("name")
query = self.request.GET.get("q", "").strip()

if query:
queryset = queryset.filter(
Q(name__icontains=query)
| Q(description__icontains=query)
| Q(language__icontains=query)
| Q(provider__name__icontains=query)
| Q(skill__name__icontains=query)
).distinct()

return queryset

def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
"""Add the current search query to the template context."""
context = super().get_context_data(**kwargs)
context["query"] = self.request.GET.get("q", "").strip()
return context
return LearningResource.objects.select_related("provider").order_by("name")


class ToolsLanguagesMethodologiesPageView(SingleTableView):
Expand Down
9 changes: 5 additions & 4 deletions prod-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --extra=prod --output-file=prod-requirements.txt
# pip-compile --extra=prod --no-index --output-file=prod-requirements.txt
#
asgiref==3.13.0
# via django
Expand All @@ -22,6 +22,7 @@ django==6.1.2
# direct_webapp (pyproject.toml)
# django-bootstrap5
# django-crispy-forms
# django-filter
# django-import-export
# django-multiselectfield
# django-registration
Expand All @@ -33,6 +34,8 @@ django-crispy-forms==2.7
# via
# crispy-bootstrap5
# direct_webapp (pyproject.toml)
django-filter==26.2
# via direct_webapp (pyproject.toml)
django-import-export==4.4.4
# via direct_webapp (pyproject.toml)
django-multiselectfield==1.0.1
Expand All @@ -53,8 +56,6 @@ mysqlclient==2.3.0
# via direct_webapp (pyproject.toml)
nh3==0.3.7
# via direct_webapp (pyproject.toml)
packaging==26.3
# via gunicorn
requests==2.34.2
# via direct_webapp (pyproject.toml)
sqlparse==0.6.0
Expand Down
8 changes: 7 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ dependencies = [
"django-tables2",
"markdown",
"nh3",
"requests"
"requests",
"django-filter"

]

[project.optional-dependencies]
Expand Down Expand Up @@ -94,6 +96,10 @@ follow_untyped_imports = true
module = ["django_tables2.*"]
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["django_filters.*"]
ignore_missing_imports = true

[[tool.mypy.overrides]]
module = ["main.io_resources"]
disallow_any_generics = false
Expand Down
9 changes: 5 additions & 4 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#
# This file is autogenerated by pip-compile with Python 3.12
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile
# pip-compile --no-index
#
asgiref==3.13.0
# via django
Expand All @@ -22,6 +22,7 @@ django==6.1.2
# direct_webapp (pyproject.toml)
# django-bootstrap5
# django-crispy-forms
# django-filter
# django-import-export
# django-multiselectfield
# django-registration
Expand All @@ -33,6 +34,8 @@ django-crispy-forms==2.7
# via
# crispy-bootstrap5
# direct_webapp (pyproject.toml)
django-filter==26.2
# via direct_webapp (pyproject.toml)
django-import-export==4.4.4
# via direct_webapp (pyproject.toml)
django-multiselectfield==1.0.1
Expand All @@ -51,8 +54,6 @@ markdown==3.11.2
# via direct_webapp (pyproject.toml)
nh3==0.3.7
# via direct_webapp (pyproject.toml)
packaging==26.3
# via gunicorn
requests==2.34.2
# via direct_webapp (pyproject.toml)
sqlparse==0.6.0
Expand Down
Loading
Loading