From 529c1a309259941f245b8a820ed80a212c02ebf2 Mon Sep 17 00:00:00 2001 From: Jhonatan Lopes Date: Wed, 21 Jun 2023 08:25:11 -0300 Subject: [PATCH] Libraries BaseLibraryPage (#10765) * Libraries profile detail page * Define detail cards for RCC and RH * Add Library base detail page * Add abstract Library detail page * Remove `rcc` and `research` prefixes from models * Fix image height and width attributes * Abstract detail page factory * Lint * Better error messages * Fix mistyped factory model * Add docstrings * Rename JS file * Rename libraries filter_form * Base libraries LibraryPage * Change years to year on filter form * Add an abstract BaseLibraryPage * Add libraries level `constants` module * Improve API * Fix tests * Lint * Fix type annotation imports * Remove duplicated block --- esbuild.config.js | 4 +- .../filter_form.html} | 0 .../pages/libraries/library_page.html | 111 ++++++++++++++ .../pages/libraries/rcc/library_page.html | 135 +++------------- .../libraries/research_hub/library_page.html | 135 +++------------- .../libraries/{rcc => }/constants.py | 0 .../pagemodels/libraries/library_page.py | 142 +++++++++++++++++ .../pagemodels/libraries/rcc/authors_index.py | 9 +- .../pagemodels/libraries/rcc/library_page.py | 144 ++++-------------- .../libraries/research_hub/authors_index.py | 4 +- .../libraries/research_hub/constants.py | 36 ----- .../libraries/research_hub/forms.py | 2 +- .../libraries/research_hub/library_page.py | 137 ++++------------- .../tests/libraries/rcc/test_library_page.py | 92 ++++++----- .../libraries/research_hub/test_forms.py | 4 +- .../research_hub/test_library_page.py | 76 +++++---- ...b-library.js => libraries-library-page.js} | 0 17 files changed, 455 insertions(+), 576 deletions(-) rename network-api/networkapi/templates/fragments/{research_library_form.html => libraries/filter_form.html} (100%) create mode 100644 network-api/networkapi/templates/pages/libraries/library_page.html rename network-api/networkapi/wagtailpages/pagemodels/libraries/{rcc => }/constants.py (100%) create mode 100644 network-api/networkapi/wagtailpages/pagemodels/libraries/library_page.py delete mode 100644 network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/constants.py rename source/js/foundation/pages/{research-hub-library.js => libraries-library-page.js} (100%) diff --git a/esbuild.config.js b/esbuild.config.js index 12414863f..53f006537 100644 --- a/esbuild.config.js +++ b/esbuild.config.js @@ -52,8 +52,8 @@ const sources = { source: `buyers-guide/editorial-content-index.js`, bundle: true, }, - "research-hub-library": { - source: `foundation/pages/research-hub-library.js`, + "libraries-library-page": { + source: `foundation/pages/libraries-library-page.js`, bundle: true, }, polyfills: { diff --git a/network-api/networkapi/templates/fragments/research_library_form.html b/network-api/networkapi/templates/fragments/libraries/filter_form.html similarity index 100% rename from network-api/networkapi/templates/fragments/research_library_form.html rename to network-api/networkapi/templates/fragments/libraries/filter_form.html diff --git a/network-api/networkapi/templates/pages/libraries/library_page.html b/network-api/networkapi/templates/pages/libraries/library_page.html new file mode 100644 index 000000000..87b8e1adc --- /dev/null +++ b/network-api/networkapi/templates/pages/libraries/library_page.html @@ -0,0 +1,111 @@ +{% extends "pages/libraries/base.html" %} +{% load i18n static wagtailcore_tags wagtailimages_tags %} +{% block library_content %} +
+ {% block breadcrumbs %}{% endblock breadcrumbs %} +

{{ page.title }}

+
+
+ {# SEARCH BAR #} + {% comment %} + The page url is necessary in the form to that the filter anchor link is not carried forward. + Say on mobile the user used the "Filter" anchor link to get to the filter section, + that adds the `#filter` part to the URL. Submitting the form in that stage without the + explicit URL would carry the anchor link forward. That would mean the view is scrolled to the + filter section again upon page reload. This seems undesireable. + {% endcomment %} +
+ {% block search_bar %}{% endblock search_bar %} +
+
+
+ {# FILTER, SORT AND RESULTS #} + {# For side-by-side layout, we need to pull the results up to that the upper end lines up with the search bar. #} +
+ {# SORT AND RESULTS #} +
+
+ {# FILTER BUTTON #} +
+ {% include "fragments/libraries/filter_button.html" with button=False %} + {% include "fragments/libraries/filter_button.html" with button=True %} +
+
+ {# SORT SELECT #} + + +
+
+
+ {# RESULTS COUNT #} + {% if search_query %} + {% blocktranslate count counter=detail_pages_count trimmed %} + {{ detail_pages_count }} result for {{ search_query }} + {% plural %} + {{ detail_pages_count }} results for {{ search_query }} + {% endblocktranslate %} + {% else %} + {% blocktranslate count counter=detail_pages_count trimmed %} + {{ detail_pages_count }} result + {% plural %} + {{ detail_pages_count }} results + {% endblocktranslate %} + {% endif %} +
+
+
    + {# RESULTS LIST #} + {% block detail_pages %} + {% endblock detail_pages %} +
+
+ {# PAGINATION #} + {% include "fragments/pagination.html" with page=detail_pages %} +
+
+
+ {# FILTER SECTION #} +
+ +
+

{% translate "Filter" %}

+
+ {% include "fragments/libraries/filter_form.html" with form=form %} +
+
+ +
+
+
+
+
+{% endblock library_content %} +{% block extra_scripts %} + {{ block.super }} + +{% endblock extra_scripts %} diff --git a/network-api/networkapi/templates/pages/libraries/rcc/library_page.html b/network-api/networkapi/templates/pages/libraries/rcc/library_page.html index a416e6e85..250d5fb1c 100644 --- a/network-api/networkapi/templates/pages/libraries/rcc/library_page.html +++ b/network-api/networkapi/templates/pages/libraries/rcc/library_page.html @@ -1,117 +1,18 @@ -{% extends "pages/libraries/base.html" %} -{% load i18n static wagtailcore_tags wagtailimages_tags breadcrumbs %} -{% block library_content %} -
- {% rcc_breadcrumbs %} -

{{ page.title }}

-
-
- {# SEARCH BAR #} - {% comment %} - The page url is necessary in the form to that the filter anchor link is not carried forward. - Say on mobile the user used the "Filter" anchor link to get to the filter section, - that adds the `#filter` part to the URL. Submitting the form in that stage without the - explicit URL would carry the anchor link forward. That would mean the view is scrolled to the - filter section again upon page reload. This seems undesireable. - {% endcomment %} -
- {% include "fragments/libraries/rcc/search_bar.html" %} -
-
-
- {# FILTER, SORT AND RESULTS #} - {# For side-by-side layout, we need to pull the results up to that the upper end lines up with the search bar. #} -
- {# SORT AND RESULTS #} -
-
- {# FILTER BUTTON #} -
- {% include "fragments/libraries/filter_button.html" with button=False %} - {% include "fragments/libraries/filter_button.html" with button=True %} -
-
- {# SORT SELECT #} - - -
-
-
- {# RESULTS COUNT #} - {% if search_query %} - {% blocktranslate count counter=rcc_detail_pages_count trimmed %} - {{ rcc_detail_pages_count }} result for {{ search_query }} - {% plural %} - {{ rcc_detail_pages_count }} results for {{ search_query }} - {% endblocktranslate %} - {% else %} - {% blocktranslate count counter=rcc_detail_pages_count trimmed %} - {{ rcc_detail_pages_count }} result - {% plural %} - {{ rcc_detail_pages_count }} results - {% endblocktranslate %} - {% endif %} -
-
-
    - {# RESULTS LIST #} - {% for rcc_detail_page in rcc_detail_pages %} -
  • - {% include "fragments/libraries/rcc/detail_card.html" with rcc_detail_page=rcc_detail_page hide_authors=False hide_image_on_mobile=True hide_related_content_types_on_mobile=True %} -
  • - {% endfor %} -
-
- {# PAGINATION #} - {% include "fragments/pagination.html" with page=rcc_detail_pages %} -
-
-
- {# FILTER SECTION #} -
- -
-

{% translate "Filter" %}

-
- {% include "fragments/research_library_form.html" with form=form %} -
-
- -
-
-
-
-
-{% endblock library_content %} -{% block extra_scripts %} - {{ block.super }} - -{% endblock extra_scripts %} \ No newline at end of file +{% extends "pages/libraries/library_page.html" %} +{% load static breadcrumbs %} + +{% block breadcrumbs %} + {% rcc_breadcrumbs %} +{% endblock breadcrumbs %} + +{% block search_bar %} + {% include "fragments/libraries/rcc/search_bar.html" %} +{% endblock search_bar %} + +{% block detail_pages %} + {% for detail_page in detail_pages %} +
  • + {% include "fragments/libraries/rcc/detail_card.html" with rcc_detail_page=detail_page hide_authors=False hide_image_on_mobile=True hide_related_content_types_on_mobile=True %} +
  • + {% endfor %} +{% endblock detail_pages %} diff --git a/network-api/networkapi/templates/pages/libraries/research_hub/library_page.html b/network-api/networkapi/templates/pages/libraries/research_hub/library_page.html index d45ab7082..46bd625ba 100644 --- a/network-api/networkapi/templates/pages/libraries/research_hub/library_page.html +++ b/network-api/networkapi/templates/pages/libraries/research_hub/library_page.html @@ -1,117 +1,18 @@ -{% extends "pages/libraries/base.html" %} -{% load i18n static wagtailcore_tags wagtailimages_tags breadcrumbs %} -{% block library_content %} -
    - {% research_breadcrumbs %} -

    {{ page.title }}

    -
    -
    - {# SEARCH BAR #} - {% comment %} - The page url is necessary in the form to that the filter anchor link is not carried forward. - Say on mobile the user used the "Filter" anchor link to get to the filter section, - that adds the `#filter` part to the URL. Submitting the form in that stage without the - explicit URL would carry the anchor link forward. That would mean the view is scrolled to the - filter section again upon page reload. This seems undesireable. - {% endcomment %} -
    - {% include "fragments/libraries/research_hub/search_bar.html" %} -
    -
    -
    - {# FILTER, SORT AND RESULTS #} - {# For side-by-side layout, we need to pull the results up to that the upper end lines up with the search bar. #} -
    - {# SORT AND RESULTS #} -
    -
    - {# FILTER BUTTON #} -
    - {% include "fragments/libraries/filter_button.html" with button=False %} - {% include "fragments/libraries/filter_button.html" with button=True %} -
    -
    - {# SORT SELECT #} - - -
    -
    -
    - {# RESULTS COUNT #} - {% if search_query %} - {% blocktranslate count counter=research_detail_pages_count trimmed %} - {{ research_detail_pages_count }} result for {{ search_query }} - {% plural %} - {{ research_detail_pages_count }} results for {{ search_query }} - {% endblocktranslate %} - {% else %} - {% blocktranslate count counter=research_detail_pages_count trimmed %} - {{ research_detail_pages_count }} result - {% plural %} - {{ research_detail_pages_count }} results - {% endblocktranslate %} - {% endif %} -
    -
    -
      - {# RESULTS LIST #} - {% for research_detail_page in research_detail_pages %} -
    • - {% include "fragments/libraries/research_hub/detail_card.html" with research_detail_page=research_detail_page hide_image_on_mobile=True hide_related_topics_on_mobile=True %} -
    • - {% endfor %} -
    -
    - {# PAGINATION #} - {% include "fragments/pagination.html" with page=research_detail_pages %} -
    -
    -
    - {# FILTER SECTION #} -
    - -
    -

    {% translate 'Filter' %}

    -
    - {% include "fragments/research_library_form.html" with form=form %} -
    -
    - -
    -
    -
    -
    -
    -{% endblock library_content %} -{% block extra_scripts %} - {{ block.super }} - -{% endblock extra_scripts %} +{% extends "pages/libraries/library_page.html" %} +{% load static breadcrumbs %} + +{% block breadcrumbs %} + {% research_breadcrumbs %} +{% endblock breadcrumbs %} + +{% block search_bar %} + {% include "fragments/libraries/research_hub/search_bar.html" %} +{% endblock search_bar %} + +{% block detail_pages %} + {% for detail_page in detail_pages %} +
  • + {% include "fragments/libraries/rcc/detail_card.html" with rcc_detail_page=detail_page hide_authors=False hide_image_on_mobile=True hide_related_content_types_on_mobile=True %} +
  • + {% endfor %} +{% endblock detail_pages %} diff --git a/network-api/networkapi/wagtailpages/pagemodels/libraries/rcc/constants.py b/network-api/networkapi/wagtailpages/pagemodels/libraries/constants.py similarity index 100% rename from network-api/networkapi/wagtailpages/pagemodels/libraries/rcc/constants.py rename to network-api/networkapi/wagtailpages/pagemodels/libraries/constants.py diff --git a/network-api/networkapi/wagtailpages/pagemodels/libraries/library_page.py b/network-api/networkapi/wagtailpages/pagemodels/libraries/library_page.py new file mode 100644 index 000000000..795af45d2 --- /dev/null +++ b/network-api/networkapi/wagtailpages/pagemodels/libraries/library_page.py @@ -0,0 +1,142 @@ +import typing +from functools import cached_property +from typing import Optional + +from django.core import paginator +from django.db import models +from wagtail import images as wagtail_images +from wagtail.admin import panels +from wagtail.images import edit_handlers as image_panels +from wagtail_localize.fields import SynchronizedField, TranslatableField + +from networkapi.wagtailpages.pagemodels.base import BasePage +from networkapi.wagtailpages.pagemodels.libraries import constants + +if typing.TYPE_CHECKING: + from django import forms, http + from django import template as django_template + from django.db.models.query import QuerySet + + +class BaseLibraryPage(BasePage): + """An abstract template for a library page. + + To define a concrete library page, subclass it and implement the following attributes: + - `parent_page_types`: Return the parent page types for this page. + - `subpage_types`: Return the subpage types for this page. + - `template`: Return the template to use for this page. + + In addition, the following methods have to be implemented: + - `get_form`: Return the form class used to filter detail pages. + - `get_filtered_detail_pages`: Return the article detail pages that match the given filters in the form. + + Concrete implementation examples can be found in the RCC and Research_Hub apps. + """ + + max_count = 1 + + template = "pages/libraries/library_page.html" + + SORT_CHOICES = constants.SORT_CHOICES + + banner_image = models.ForeignKey( + wagtail_images.get_image_model_string(), + null=True, + blank=True, + on_delete=models.SET_NULL, + ) + + results_count = models.PositiveSmallIntegerField( + default=10, + help_text="Maximum number of results to be displayed per page.", + ) + + content_panels = BasePage.content_panels + [ + image_panels.FieldPanel("banner_image"), + ] + + settings_panels = BasePage.settings_panels + [panels.FieldPanel("results_count")] + + translatable_fields = [ + # Content tab fields + TranslatableField("title"), + # Promote tab fields + SynchronizedField("slug"), + TranslatableField("seo_title"), + SynchronizedField("show_in_menus"), + TranslatableField("search_description"), + SynchronizedField("search_image"), + ] + + class Meta: + abstract = True + + @property + def filter_form(self): + """Form class used to filter detail pages for this page.""" + raise NotImplementedError("Please implement this property in your subclass.") + + @cached_property + def detail_pages(self): + """Return the article detail pages that are children of this page.""" + raise NotImplementedError("Please implement this property in your subclass.") + + @staticmethod + def filter_detail_pages(pages: "QuerySet", filter_form: "forms.Form") -> "QuerySet": + """Return the article detail pages that match the given filters in the `filter_form`.""" + raise NotImplementedError("Please implement this method in your subclass.") + + def get_sorted_filtered_detail_pages( + self, + *, + filter_form: Optional["forms.Form"] = None, + sort: constants.SortOption = constants.SORT_NEWEST_FIRST, + search_query: Optional[str] = None, + ) -> "QuerySet": + """Get sorted article detail pages filtered by the form options and search parameters.""" + detail_pages = self.detail_pages + + if filter_form: + detail_pages = self.filter_detail_pages(detail_pages, filter_form) + + detail_pages = detail_pages.order_by(sort.order_by_value) + + if search_query: + detail_pages = detail_pages.search( + search_query, + order_by_relevance=False, # To preserve original ordering + ) + + return detail_pages + + def get_context(self, request: "http.HttpRequest") -> "django_template.Context": + search_query: str = request.GET.get("search", "") + sort_value: str = request.GET.get("sort", "") + sort: constants.SortOption = constants.SORT_CHOICES.get(sort_value, constants.SORT_NEWEST_FIRST) + + Form = self.filter_form + filter_form = Form(request.GET, label_suffix="") + + sorted_and_searched_and_filtered_detail_pages = self.get_sorted_filtered_detail_pages( + filter_form=filter_form, search_query=search_query, sort=sort + ) + + detail_pages_paginator = paginator.Paginator( + object_list=sorted_and_searched_and_filtered_detail_pages, + per_page=self.results_count, + allow_empty_first_page=True, + ) + + page: Optional[str] = request.GET.get("page") + detail_pages_page = detail_pages_paginator.get_page(page) + + context: "django_template.Context" = super().get_context(request) + context["search_query"] = search_query + context["sort"] = sort + context["form"] = filter_form + context["detail_pages_count"] = detail_pages_paginator.count + context["detail_pages"] = detail_pages_page + return context + + def get_banner(self): + return self.banner_image diff --git a/network-api/networkapi/wagtailpages/pagemodels/libraries/rcc/authors_index.py b/network-api/networkapi/wagtailpages/pagemodels/libraries/rcc/authors_index.py index a345f7b98..033a58603 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/libraries/rcc/authors_index.py +++ b/network-api/networkapi/wagtailpages/pagemodels/libraries/rcc/authors_index.py @@ -9,11 +9,8 @@ from wagtail_localize.fields import SynchronizedField, TranslatableField from networkapi.wagtailpages import utils from networkapi.wagtailpages.pagemodels import profiles from networkapi.wagtailpages.pagemodels.base import BasePage -from networkapi.wagtailpages.pagemodels.libraries.rcc import ( - constants, - detail_page, - library_page, -) +from networkapi.wagtailpages.pagemodels.libraries import constants as base_constants +from networkapi.wagtailpages.pagemodels.libraries.rcc import detail_page, library_page class RCCAuthorsIndexPage( @@ -94,7 +91,7 @@ class RCCAuthorsIndexPage( def get_latest_author_rcc_entries(self, author_profile): author_articles = self.get_author_rcc_entries(author_profile) author_articles = author_articles.order_by("-original_publication_date") - latest_articles = author_articles[: constants.LATEST_ARTICLES_COUNT] + latest_articles = author_articles[: base_constants.LATEST_ARTICLES_COUNT] return latest_articles def get_author_rcc_entries_count(self, author_profile): diff --git a/network-api/networkapi/wagtailpages/pagemodels/libraries/rcc/library_page.py b/network-api/networkapi/wagtailpages/pagemodels/libraries/rcc/library_page.py index efa7cbf53..76da91817 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/libraries/rcc/library_page.py +++ b/network-api/networkapi/wagtailpages/pagemodels/libraries/rcc/library_page.py @@ -1,134 +1,59 @@ import typing -from typing import Optional +from functools import cached_property -from django.core import paginator -from django.db import models -from wagtail import images as wagtail_images from wagtail import models as wagtail_models -from wagtail.admin import panels -from wagtail.images import edit_handlers as image_panels -from wagtail_localize.fields import SynchronizedField, TranslatableField from networkapi.wagtailpages import utils from networkapi.wagtailpages.pagemodels import profiles as profile_models -from networkapi.wagtailpages.pagemodels.base import BasePage -from networkapi.wagtailpages.pagemodels.libraries.rcc import ( - constants, - detail_page, - taxonomies, +from networkapi.wagtailpages.pagemodels.libraries import ( + library_page as base_library_page, ) +from networkapi.wagtailpages.pagemodels.libraries.rcc import detail_page, taxonomies from networkapi.wagtailpages.pagemodels.libraries.rcc.forms import ( RCCLibraryPageFilterForm, ) if typing.TYPE_CHECKING: - from django import http - from django import template as django_template + from django import forms + from django.db.models.query import QuerySet -class RCCLibraryPage(BasePage): - max_count = 1 - +class RCCLibraryPage(base_library_page.BaseLibraryPage): parent_page_types = ["RCCLandingPage"] subpage_types = ["RCCDetailPage"] template = "pages/libraries/rcc/library_page.html" - SORT_CHOICES = constants.SORT_CHOICES + @property + def filter_form(self) -> "forms.Form": + """Form class used to filter detail pages for this page.""" + return RCCLibraryPageFilterForm - banner_image = models.ForeignKey( - wagtail_images.get_image_model_string(), - null=True, - blank=True, - on_delete=models.SET_NULL, - ) - - results_count = models.PositiveSmallIntegerField( - default=10, - help_text="Maximum number of results to be displayed per page.", - ) - - content_panels = BasePage.content_panels + [ - image_panels.FieldPanel("banner_image"), - ] - - settings_panels = BasePage.settings_panels + [panels.FieldPanel("results_count")] - - translatable_fields = [ - # Content tab fields - TranslatableField("title"), - # Promote tab fields - SynchronizedField("slug"), - TranslatableField("seo_title"), - SynchronizedField("show_in_menus"), - TranslatableField("search_description"), - SynchronizedField("search_image"), - ] - - def get_context(self, request: "http.HttpRequest") -> "django_template.Context": - search_query: str = request.GET.get("search", "") - sort_value: str = request.GET.get("sort", "") - sort: constants.SortOption = constants.SORT_CHOICES.get(sort_value, constants.SORT_NEWEST_FIRST) - - filter_form = RCCLibraryPageFilterForm(request.GET, label_suffix="") + @cached_property + def detail_pages(self) -> "QuerySet[detail_page.RCCDetailPage]": + """Return the article detail pages that are children of this page.""" + return detail_page.RCCDetailPage.objects.live().public().filter(locale=wagtail_models.Locale.get_active()) + @staticmethod + def filter_detail_pages( + pages: "QuerySet[detail_page.RCCDetailPage]", filter_form: "forms.Form" + ) -> "QuerySet[detail_page.RCCDetailPage]": + """Return the article detail pages that match the given filters in the form.""" if filter_form.is_valid(): - filtered_author_ids: list[int] = filter_form.cleaned_data["authors"] - filtered_content_type_ids: list[int] = filter_form.cleaned_data["content_types"] - filtered_curricular_area_ids: list[int] = filter_form.cleaned_data["curricular_areas"] - filtered_topic_ids: list[int] = filter_form.cleaned_data["topics"] + author_profile_ids: list[int] = filter_form.cleaned_data["authors"] + content_type_ids: list[int] = filter_form.cleaned_data["content_types"] + curricular_area_ids: list[int] = filter_form.cleaned_data["curricular_areas"] + topic_ids: list[int] = filter_form.cleaned_data["topics"] else: # If the form is not valid, we will not filter by any of the values. # This will result in all articles being displayed. - filtered_author_ids = [] - filtered_content_type_ids = [] - filtered_curricular_area_ids = [] - filtered_topic_ids = [] + author_profile_ids = [] + content_type_ids = [] + curricular_area_ids = [] + topic_ids = [] - searched_and_filtered_rcc_detail_pages = self._get_rcc_detail_pages( - search=search_query, - sort=sort, - author_profile_ids=filtered_author_ids, - content_type_ids=filtered_content_type_ids, - curricular_area_ids=filtered_curricular_area_ids, - topic_ids=filtered_topic_ids, - ) - - rcc_detail_pages_paginator = paginator.Paginator( - object_list=searched_and_filtered_rcc_detail_pages, - per_page=self.results_count, - allow_empty_first_page=True, - ) - - page: Optional[str] = request.GET.get("page") - rcc_detail_pages_page = rcc_detail_pages_paginator.get_page(page) - - context: "django_template.Context" = super().get_context(request) - context["search_query"] = search_query - context["sort"] = sort - context["form"] = filter_form - context["rcc_detail_pages_count"] = rcc_detail_pages_paginator.count - context["rcc_detail_pages"] = rcc_detail_pages_page - return context - - def _get_rcc_detail_pages( - self, - *, - search: str = "", - sort: constants.SortOption = constants.SORT_NEWEST_FIRST, - author_profile_ids: Optional[list[int]] = None, - content_type_ids: Optional[list[int]] = None, - curricular_area_ids: Optional[list[int]] = None, - topic_ids: Optional[list[int]] = None, - ): - author_profile_ids = author_profile_ids or [] - content_type_ids = content_type_ids or [] - curricular_area_ids = curricular_area_ids or [] - topic_ids = topic_ids or [] - - rcc_detail_pages = detail_page.RCCDetailPage.objects.live().public() - rcc_detail_pages = rcc_detail_pages.filter(locale=wagtail_models.Locale.get_active()) + rcc_detail_pages = pages author_profiles = utils.get_rcc_authors(profile_models.Profile.objects.all()) author_profiles = author_profiles.filter(id__in=author_profile_ids) @@ -157,15 +82,4 @@ class RCCLibraryPage(BasePage): for topic in topics: rcc_detail_pages = rcc_detail_pages.filter(related_topics__topic__translation_key=topic.translation_key) - rcc_detail_pages = rcc_detail_pages.order_by(sort.order_by_value) - - if search: - rcc_detail_pages = rcc_detail_pages.search( - search, - order_by_relevance=False, # To preserve original ordering - ) - return rcc_detail_pages - - def get_banner(self): - return self.banner_image diff --git a/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/authors_index.py b/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/authors_index.py index d636710f2..1a1d8ebc8 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/authors_index.py +++ b/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/authors_index.py @@ -9,8 +9,8 @@ from wagtail_localize.fields import SynchronizedField, TranslatableField from networkapi.wagtailpages import utils from networkapi.wagtailpages.pagemodels import profiles from networkapi.wagtailpages.pagemodels.base import BasePage +from networkapi.wagtailpages.pagemodels.libraries import constants as base_constants from networkapi.wagtailpages.pagemodels.libraries.research_hub import ( - constants, detail_page, library_page, ) @@ -95,7 +95,7 @@ class ResearchAuthorsIndexPage( def get_latest_author_research(self, author_profile): author_research = self.get_author_research(author_profile) author_research = author_research.order_by("-original_publication_date") - latest_research = author_research[: constants.LATEST_RESEARCH_COUNT_LIMIT] + latest_research = author_research[: base_constants.LATEST_ARTICLES_COUNT] return latest_research def get_author_research_count(self, author_profile): diff --git a/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/constants.py b/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/constants.py deleted file mode 100644 index 4884c882e..000000000 --- a/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/constants.py +++ /dev/null @@ -1,36 +0,0 @@ -import collections - -from django.utils.translation import gettext_lazy as _ - -# We don't want to expose the actual database column value that we use for sorting. -# Therefore, we need a separate value that is used in the form and url. -SortOption = collections.namedtuple("SortOption", ["label", "value", "order_by_value"]) - -SORT_NEWEST_FIRST = SortOption( - label=_("Newest first"), - value="newest-first", - order_by_value="-original_publication_date", -) -SORT_OLDEST_FIRST = SortOption( - label=_("Oldest first"), - value="oldest-first", - order_by_value="original_publication_date", -) -SORT_ALPHABETICAL = SortOption( - label=_("Alphabetical (A-Z)"), - value="alphabetical", - order_by_value="title", -) -SORT_ALPHABETICAL_REVERSED = SortOption( - label=_("Alphabetical (Z-A)"), - value="alphabetical-reversed", - order_by_value="-title", -) -SORT_CHOICES = { - SORT_NEWEST_FIRST.value: SORT_NEWEST_FIRST, - SORT_OLDEST_FIRST.value: SORT_OLDEST_FIRST, - SORT_ALPHABETICAL.value: SORT_ALPHABETICAL, - SORT_ALPHABETICAL_REVERSED.value: SORT_ALPHABETICAL_REVERSED, -} - -LATEST_RESEARCH_COUNT_LIMIT = 3 diff --git a/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/forms.py b/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/forms.py index 84edc851f..97ab20193 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/forms.py +++ b/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/forms.py @@ -48,7 +48,7 @@ class ResearchLibraryPageFilterForm(forms.Form): choices=_get_topic_options, label=pgettext_lazy("Filter form field label", "Topics"), ) - years = forms.ChoiceField( + year = forms.ChoiceField( required=False, choices=_get_year_options, widget=forms.RadioSelect(attrs={"class": "rh-radio"}), diff --git a/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/library_page.py b/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/library_page.py index 6d27fbc67..da293031f 100644 --- a/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/library_page.py +++ b/network-api/networkapi/wagtailpages/pagemodels/libraries/research_hub/library_page.py @@ -1,19 +1,15 @@ import typing +from functools import cached_property from typing import Optional -from django.core import paginator -from django.db import models -from wagtail import images as wagtail_images from wagtail import models as wagtail_models -from wagtail.admin import panels -from wagtail.images import edit_handlers as image_panels -from wagtail_localize.fields import SynchronizedField, TranslatableField from networkapi.wagtailpages import utils from networkapi.wagtailpages.pagemodels import profiles as profile_models -from networkapi.wagtailpages.pagemodels.base import BasePage +from networkapi.wagtailpages.pagemodels.libraries import ( + library_page as base_library_page, +) from networkapi.wagtailpages.pagemodels.libraries.research_hub import ( - constants, detail_page, taxonomies, ) @@ -22,110 +18,46 @@ from networkapi.wagtailpages.pagemodels.libraries.research_hub.forms import ( ) if typing.TYPE_CHECKING: - from django import http - from django import template as django_template + from django import forms + from django.db.models.query import QuerySet -class ResearchLibraryPage(BasePage): - max_count = 1 - +class ResearchLibraryPage(base_library_page.BaseLibraryPage): parent_page_types = ["ResearchLandingPage"] subpage_types = ["ResearchDetailPage"] template = "pages/libraries/research_hub/library_page.html" - SORT_CHOICES = constants.SORT_CHOICES + @property + def filter_form(self) -> "forms.Form": + """Form class used to filter detail pages for this page.""" + return ResearchLibraryPageFilterForm - banner_image = models.ForeignKey( - wagtail_images.get_image_model_string(), - null=True, - blank=True, - on_delete=models.SET_NULL, - ) + @cached_property + def detail_pages(self) -> "QuerySet[detail_page.ResearchDetailPage]": + """Return the article detail pages that are children of this page.""" + return detail_page.ResearchDetailPage.objects.live().public().filter(locale=wagtail_models.Locale.get_active()) - results_count = models.PositiveSmallIntegerField( - default=10, - help_text="Maximum number of results to be displayed per page.", - ) - - content_panels = BasePage.content_panels + [ - image_panels.FieldPanel("banner_image"), - ] - - settings_panels = BasePage.settings_panels + [panels.FieldPanel("results_count")] - - translatable_fields = [ - # Content tab fields - TranslatableField("title"), - # Promote tab fields - SynchronizedField("slug"), - TranslatableField("seo_title"), - SynchronizedField("show_in_menus"), - TranslatableField("search_description"), - SynchronizedField("search_image"), - ] - - def get_context(self, request: "http.HttpRequest") -> "django_template.Context": - search_query: str = request.GET.get("search", "") - sort_value: str = request.GET.get("sort", "") - sort: constants.SortOption = constants.SORT_CHOICES.get(sort_value, constants.SORT_NEWEST_FIRST) - - filter_form = ResearchLibraryPageFilterForm(request.GET, label_suffix="") + @staticmethod + def filter_detail_pages( + pages: "QuerySet[detail_page.ResearchDetailPage]", filter_form: "forms.Form" + ) -> "QuerySet[detail_page.ResearchDetailPage]": + """Return the article detail pages that match the given filters in the form.""" if filter_form.is_valid(): - filtered_author_ids: list[int] = filter_form.cleaned_data["authors"] - filtered_topic_ids: list[int] = filter_form.cleaned_data["topics"] - filtered_region_ids: list[int] = filter_form.cleaned_data["regions"] - filtered_year: Optional[int] = filter_form.cleaned_data["years"] + author_profile_ids: list[int] = filter_form.cleaned_data["authors"] + topic_ids: list[int] = filter_form.cleaned_data["topics"] + region_ids: list[int] = filter_form.cleaned_data["regions"] + year: Optional[int] = filter_form.cleaned_data["year"] else: # If the form is not valid, we will not filter by any of the values. # This will result in all research being displayed. - filtered_author_ids = [] - filtered_topic_ids = [] - filtered_region_ids = [] - filtered_year = None + author_profile_ids = [] + topic_ids = [] + region_ids = [] + year = None - searched_and_filtered_research_detail_pages = self._get_research_detail_pages( - search=search_query, - sort=sort, - author_profile_ids=filtered_author_ids, - topic_ids=filtered_topic_ids, - region_ids=filtered_region_ids, - year=filtered_year, - ) - research_detail_pages_paginator = paginator.Paginator( - object_list=searched_and_filtered_research_detail_pages, - per_page=self.results_count, - allow_empty_first_page=True, - ) - - page: Optional[str] = request.GET.get("page") - research_detail_pages_page = research_detail_pages_paginator.get_page(page) - - context: "django_template.Context" = super().get_context(request) - context["search_query"] = search_query - context["sort"] = sort - context["form"] = filter_form - context["research_detail_pages_count"] = research_detail_pages_paginator.count - context["research_detail_pages"] = research_detail_pages_page - return context - - def _get_research_detail_pages( - self, - *, - search: str = "", - sort: constants.SortOption = constants.SORT_NEWEST_FIRST, - author_profile_ids: Optional[list[int]] = None, - topic_ids: Optional[list[int]] = None, - region_ids: Optional[list[int]] = None, - year: Optional[int] = None, - ): - author_profile_ids = author_profile_ids or [] - topic_ids = topic_ids or [] - region_ids = region_ids or [] - - research_detail_pages = detail_page.ResearchDetailPage.objects.live().public() - research_detail_pages = research_detail_pages.filter(locale=wagtail_models.Locale.get_active()) + research_detail_pages = pages author_profiles = utils.get_research_authors(profile_models.Profile.objects.all()) author_profiles = author_profiles.filter(id__in=author_profile_ids) @@ -153,15 +85,4 @@ class ResearchLibraryPage(BasePage): if year: research_detail_pages = research_detail_pages.filter(original_publication_date__year=year) - research_detail_pages = research_detail_pages.order_by(sort.order_by_value) - - if search: - research_detail_pages = research_detail_pages.search( - search, - order_by_relevance=False, # To preserve original ordering - ) - return research_detail_pages - - def get_banner(self): - return self.banner_image diff --git a/network-api/networkapi/wagtailpages/tests/libraries/rcc/test_library_page.py b/network-api/networkapi/wagtailpages/tests/libraries/rcc/test_library_page.py index 0c8a4df96..0144f5978 100644 --- a/network-api/networkapi/wagtailpages/tests/libraries/rcc/test_library_page.py +++ b/network-api/networkapi/wagtailpages/tests/libraries/rcc/test_library_page.py @@ -11,7 +11,10 @@ from networkapi.wagtailpages.factory.libraries.rcc import relations as relations from networkapi.wagtailpages.factory.libraries.rcc import ( taxonomies as taxonomies_factory, ) -from networkapi.wagtailpages.pagemodels.libraries.rcc import constants +from networkapi.wagtailpages.pagemodels.libraries import constants +from networkapi.wagtailpages.pagemodels.libraries.rcc.forms import ( + RCCLibraryPageFilterForm, +) from networkapi.wagtailpages.tests.libraries.rcc import base as rcc_test_base from networkapi.wagtailpages.tests.libraries.rcc import utils as rcc_test_utils @@ -21,7 +24,7 @@ class TestRCCLibraryPage(rcc_test_base.RCCTestCase): with open(os.devnull, "w") as f: management.call_command("update_index", verbosity=0, stdout=f) - def test_get_rcc_detail_pages(self): + def testget_sorted_filtered_detail_pages(self): detail_page_1 = detail_page_factory.RCCDetailPageFactory( parent=self.library_page, ) @@ -29,13 +32,13 @@ class TestRCCLibraryPage(rcc_test_base.RCCTestCase): parent=self.library_page, ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages() + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages() self.assertEqual(len(rcc_detail_pages), 2) self.assertIn(detail_page_1, rcc_detail_pages) self.assertIn(detail_page_2, rcc_detail_pages) - def test_get_rcc_detail_pages_with_translation_aliases(self): + def testget_sorted_filtered_detail_pages_with_translation_aliases(self): detail_page_1 = detail_page_factory.RCCDetailPageFactory( parent=self.library_page, ) @@ -46,7 +49,7 @@ class TestRCCLibraryPage(rcc_test_base.RCCTestCase): fr_detail_page_1 = detail_page_1.get_translation(self.fr_locale) fr_detail_page_2 = detail_page_2.get_translation(self.fr_locale) - rcc_detail_pages = self.library_page._get_rcc_detail_pages() + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages() self.assertEqual(len(rcc_detail_pages), 2) self.assertIn(detail_page_1, rcc_detail_pages) @@ -63,7 +66,7 @@ class TestRCCLibraryPage(rcc_test_base.RCCTestCase): ) self.make_page_private(private_detail_page) - rcc_detail_pages = self.library_page._get_rcc_detail_pages() + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages() self.assertEqual(len(rcc_detail_pages), 1) self.assertIn(public_detail_page, rcc_detail_pages) self.assertNotIn(private_detail_page, rcc_detail_pages) @@ -78,7 +81,7 @@ class TestRCCLibraryPage(rcc_test_base.RCCTestCase): original_publication_date=rcc_test_utils.days_ago(1), ) - rcc_detail_pages = list(self.library_page._get_rcc_detail_pages(sort=constants.SORT_NEWEST_FIRST)) + rcc_detail_pages = list(self.library_page.get_sorted_filtered_detail_pages(sort=constants.SORT_NEWEST_FIRST)) newest_page_index = rcc_detail_pages.index(newest_page) oldest_page_index = rcc_detail_pages.index(oldest_page) @@ -94,7 +97,7 @@ class TestRCCLibraryPage(rcc_test_base.RCCTestCase): original_publication_date=rcc_test_utils.days_ago(1), ) - rcc_detail_pages = list(self.library_page._get_rcc_detail_pages(sort=constants.SORT_OLDEST_FIRST)) + rcc_detail_pages = list(self.library_page.get_sorted_filtered_detail_pages(sort=constants.SORT_OLDEST_FIRST)) newest_page_index = rcc_detail_pages.index(newest_page) oldest_page_index = rcc_detail_pages.index(oldest_page) @@ -110,7 +113,7 @@ class TestRCCLibraryPage(rcc_test_base.RCCTestCase): title="Banana", ) - rcc_detail_pages = list(self.library_page._get_rcc_detail_pages(sort=constants.SORT_ALPHABETICAL)) + rcc_detail_pages = list(self.library_page.get_sorted_filtered_detail_pages(sort=constants.SORT_ALPHABETICAL)) apple_page_index = rcc_detail_pages.index(apple_page) banana_page_index = rcc_detail_pages.index(banana_page) @@ -127,13 +130,15 @@ class TestRCCLibraryPage(rcc_test_base.RCCTestCase): title="Banana", ) - rcc_detail_pages = list(self.library_page._get_rcc_detail_pages(sort=constants.SORT_ALPHABETICAL_REVERSED)) + rcc_detail_pages = list( + self.library_page.get_sorted_filtered_detail_pages(sort=constants.SORT_ALPHABETICAL_REVERSED) + ) apple_page_index = rcc_detail_pages.index(apple_page) banana_page_index = rcc_detail_pages.index(banana_page) self.assertLess(banana_page_index, apple_page_index) - def test_get_rcc_detail_pages_sort_default(self): + def testget_sorted_filtered_detail_pages_sort_default(self): detail_page_factory.RCCDetailPageFactory( parent=self.library_page, @@ -144,8 +149,10 @@ class TestRCCLibraryPage(rcc_test_base.RCCTestCase): original_publication_date=rcc_test_utils.days_ago(1), ) - default_sort_detail_pages = list(self.library_page._get_rcc_detail_pages()) - newest_first_detail_pages = list(self.library_page._get_rcc_detail_pages(sort=constants.SORT_NEWEST_FIRST)) + default_sort_detail_pages = list(self.library_page.get_sorted_filtered_detail_pages()) + newest_first_detail_pages = list( + self.library_page.get_sorted_filtered_detail_pages(sort=constants.SORT_NEWEST_FIRST) + ) self.assertEqual(default_sort_detail_pages, newest_first_detail_pages) @@ -155,7 +162,7 @@ class TestRCCLibraryPage(rcc_test_base.RCCTestCase): for _ in range(6): detail_page_factory.RCCDetailPageFactory(parent=self.library_page) - rcc_detail_pages = self.library_page._get_rcc_detail_pages() + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages() rcc_detail_pages_paginator = paginator.Paginator( object_list=rcc_detail_pages, @@ -189,7 +196,7 @@ class TestRCCLibraryPageSearch(TestRCCLibraryPage): collaborators="", ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages(search="Apple") + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(rcc_detail_pages), 1) self.assertIn(apple_page, rcc_detail_pages) self.assertNotIn(banana_page, rcc_detail_pages) @@ -210,7 +217,7 @@ class TestRCCLibraryPageSearch(TestRCCLibraryPage): collaborators="", ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages(search="Apple") + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(rcc_detail_pages), 1) self.assertIn(apple_page, rcc_detail_pages) @@ -232,7 +239,7 @@ class TestRCCLibraryPageSearch(TestRCCLibraryPage): collaborators="", ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages(search="Apple") + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(rcc_detail_pages), 1) self.assertIn(apple_page, rcc_detail_pages) @@ -254,7 +261,7 @@ class TestRCCLibraryPageSearch(TestRCCLibraryPage): collaborators="Banana", ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages(search="Apple") + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(rcc_detail_pages), 1) self.assertIn(apple_page, rcc_detail_pages) @@ -290,7 +297,7 @@ class TestRCCLibraryPageSearch(TestRCCLibraryPage): relations_factory.RCCAuthorRelationFactory(detail_page=banana_page, author_profile=banana_profile) self.update_index() - rcc_detail_pages = self.library_page._get_rcc_detail_pages(search="Apple") + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(rcc_detail_pages), 1) self.assertIn(apple_page, rcc_detail_pages) @@ -326,7 +333,7 @@ class TestRCCLibraryPageSearch(TestRCCLibraryPage): ) self.update_index() - rcc_detail_pages = self.library_page._get_rcc_detail_pages(search="Apple") + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(rcc_detail_pages), 1) self.assertIn(apple_page, rcc_detail_pages) @@ -362,7 +369,7 @@ class TestRCCLibraryPageSearch(TestRCCLibraryPage): ) self.update_index() - rcc_detail_pages = self.library_page._get_rcc_detail_pages(search="Apple") + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(rcc_detail_pages), 1) self.assertIn(apple_page, rcc_detail_pages) @@ -394,7 +401,7 @@ class TestRCCLibraryPageSearch(TestRCCLibraryPage): relations_factory.RCCDetailPageRCCTopicRelationFactory(detail_page=banana_page, topic=banana_topic) self.update_index() - rcc_detail_pages = self.library_page._get_rcc_detail_pages(search="Apple") + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(rcc_detail_pages), 1) self.assertIn(apple_page, rcc_detail_pages) @@ -415,7 +422,8 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): detail_page_2.authors.first().author_profile, ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages(author_profile_ids=[author_profile.id]) + filter_form = RCCLibraryPageFilterForm(data={"authors": [author_profile.id]}) + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_1, rcc_detail_pages) self.assertNotIn(detail_page_2, rcc_detail_pages) @@ -441,7 +449,7 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): ) # Only show the page where both profiles are authors - rcc_detail_pages = response.context["rcc_detail_pages"] + rcc_detail_pages = response.context["detail_pages"] self.assertNotIn(detail_page_1, rcc_detail_pages) self.assertIn(detail_page_2, rcc_detail_pages) @@ -474,7 +482,8 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): self.assertEqual(profile.translation_key, profile_fr.translation_key) translation.activate(self.fr_locale.language_code) - rcc_detail_pages = self.library_page.localized._get_rcc_detail_pages(author_profile_ids=[profile_fr.id]) + filter_form = RCCLibraryPageFilterForm(data={"authors": [profile_fr.id]}) + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_1_fr, rcc_detail_pages) self.assertIn(detail_page_2_fr, rcc_detail_pages) @@ -493,7 +502,8 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): related_content_types__content_type=content_type_B, ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages(content_type_ids=[content_type_A.id]) + filter_form = RCCLibraryPageFilterForm(data={"content_types": [content_type_A.id]}) + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_A, rcc_detail_pages) self.assertNotIn(detail_page_B, rcc_detail_pages) @@ -510,7 +520,8 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): related_curricular_areas__curricular_area=curricular_area_B, ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages(curricular_area_ids=[curricular_area_A.id]) + filter_form = RCCLibraryPageFilterForm(data={"curricular_areas": [curricular_area_A.id]}) + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_A, rcc_detail_pages) self.assertNotIn(detail_page_B, rcc_detail_pages) @@ -527,7 +538,8 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): related_topics__topic=topic_B, ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages(topic_ids=[topic_A.id]) + filter_form = RCCLibraryPageFilterForm(data={"topics": [topic_A.id]}) + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_A, rcc_detail_pages) self.assertNotIn(detail_page_B, rcc_detail_pages) @@ -547,9 +559,8 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): related_content_types__content_type=content_type_A, ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages( - content_type_ids=[content_type_A.id, content_type_B.id] - ) + filter_form = RCCLibraryPageFilterForm(data={"content_types": [content_type_A.id, content_type_B.id]}) + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_1, rcc_detail_pages) self.assertNotIn(detail_page_2, rcc_detail_pages) @@ -569,9 +580,8 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): related_curricular_areas__curricular_area=curricular_area_A, ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages( - curricular_area_ids=[curricular_area_A.id, curricular_area_B.id] - ) + filter_form = RCCLibraryPageFilterForm(data={"curricular_areas": [curricular_area_A.id, curricular_area_B.id]}) + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_1, rcc_detail_pages) self.assertNotIn(detail_page_2, rcc_detail_pages) @@ -589,7 +599,8 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): related_topics__topic=topic_A, ) - rcc_detail_pages = self.library_page._get_rcc_detail_pages(topic_ids=[topic_A.id, topic_B.id]) + filter_form = RCCLibraryPageFilterForm(data={"topics": [topic_A.id, topic_B.id]}) + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_1, rcc_detail_pages) self.assertNotIn(detail_page_2, rcc_detail_pages) @@ -631,7 +642,8 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): translation.activate(self.fr_locale.language_code) # Filter for the translated content type - rcc_detail_pages = self.library_page.localized._get_rcc_detail_pages(content_type_ids=[content_type_fr.id]) + filter_form = RCCLibraryPageFilterForm(data={"content_types": [content_type_fr.id]}) + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) # We should see both pages, even though the first one is not associated with the # translated content type @@ -678,9 +690,8 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): translation.activate(self.fr_locale.language_code) # Filter for the translated curricular area - rcc_detail_pages = self.library_page.localized._get_rcc_detail_pages( - curricular_area_ids=[curricular_area_fr.id] - ) + filter_form = RCCLibraryPageFilterForm(data={"curricular_area": [curricular_area_fr.id]}) + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) # We should see both pages, even though the first one is not associated with the # translated curricular area @@ -719,7 +730,8 @@ class TestRCCLibraryPageFilters(TestRCCLibraryPage): self.assertEqual(topic.translation_key, topic_fr.translation_key) translation.activate(self.fr_locale.language_code) - rcc_detail_pages = self.library_page.localized._get_rcc_detail_pages(topic_ids=[topic_fr.id]) + filter_form = RCCLibraryPageFilterForm(data={"topics": [topic_fr.id]}) + rcc_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertEqual(len(rcc_detail_pages), 2) self.assertIn(detail_page_1_fr, rcc_detail_pages) diff --git a/network-api/networkapi/wagtailpages/tests/libraries/research_hub/test_forms.py b/network-api/networkapi/wagtailpages/tests/libraries/research_hub/test_forms.py index 8840f98c6..2f1625b1f 100644 --- a/network-api/networkapi/wagtailpages/tests/libraries/research_hub/test_forms.py +++ b/network-api/networkapi/wagtailpages/tests/libraries/research_hub/test_forms.py @@ -216,7 +216,7 @@ class ResearchLibraryPageFilterFormTestCase(research_test_base.ResearchHubTestCa self.assertCountEqual(form.fields["topics"].choices, [(t.id, t.name) for t in topics]) def test_form_years(self): - """Test that the form years field is populated with the correct choices.""" + """Test that the form year field is populated with the correct choices.""" years = [timezone.now().year, timezone.now().year - 1] detail_page_factory.ResearchDetailPageFactory( parent=self.library_page, @@ -228,7 +228,7 @@ class ResearchLibraryPageFilterFormTestCase(research_test_base.ResearchHubTestCa ) form = ResearchLibraryPageFilterForm() - self.assertCountEqual(form.fields["years"].choices, [("", "Any")] + [(y, y) for y in years]) + self.assertCountEqual(form.fields["year"].choices, [("", "Any")] + [(y, y) for y in years]) def test_form_regions(self): """Test that the form regions field is populated with the correct choices.""" diff --git a/network-api/networkapi/wagtailpages/tests/libraries/research_hub/test_library_page.py b/network-api/networkapi/wagtailpages/tests/libraries/research_hub/test_library_page.py index ace87ba33..5794d6324 100644 --- a/network-api/networkapi/wagtailpages/tests/libraries/research_hub/test_library_page.py +++ b/network-api/networkapi/wagtailpages/tests/libraries/research_hub/test_library_page.py @@ -14,7 +14,10 @@ from networkapi.wagtailpages.factory.libraries.research_hub import ( from networkapi.wagtailpages.factory.libraries.research_hub import ( taxonomies as taxonomies_factory, ) -from networkapi.wagtailpages.pagemodels.libraries.research_hub import constants +from networkapi.wagtailpages.pagemodels.libraries import constants +from networkapi.wagtailpages.pagemodels.libraries.research_hub.forms import ( + ResearchLibraryPageFilterForm, +) from networkapi.wagtailpages.tests.libraries.research_hub import ( base as research_test_base, ) @@ -36,7 +39,7 @@ class TestResearchLibraryPage(research_test_base.ResearchHubTestCase): parent=self.library_page, ) - research_detail_pages = self.library_page._get_research_detail_pages() + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages() self.assertEqual(len(research_detail_pages), 2) self.assertIn(detail_page_1, research_detail_pages) @@ -53,7 +56,7 @@ class TestResearchLibraryPage(research_test_base.ResearchHubTestCase): fr_detail_page_1 = detail_page_1.get_translation(self.fr_locale) fr_detail_page_2 = detail_page_2.get_translation(self.fr_locale) - research_detail_pages = self.library_page._get_research_detail_pages() + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages() self.assertEqual(len(research_detail_pages), 2) self.assertIn(detail_page_1, research_detail_pages) @@ -70,7 +73,7 @@ class TestResearchLibraryPage(research_test_base.ResearchHubTestCase): ) self.make_page_private(private_detail_page) - research_detail_pages = self.library_page._get_research_detail_pages() + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages() self.assertEqual(len(research_detail_pages), 1) self.assertIn(public_detail_page, research_detail_pages) self.assertNotIn(private_detail_page, research_detail_pages) @@ -85,7 +88,9 @@ class TestResearchLibraryPage(research_test_base.ResearchHubTestCase): original_publication_date=research_test_utils.days_ago(1), ) - research_detail_pages = list(self.library_page._get_research_detail_pages(sort=constants.SORT_NEWEST_FIRST)) + research_detail_pages = list( + self.library_page.get_sorted_filtered_detail_pages(sort=constants.SORT_NEWEST_FIRST) + ) newest_page_index = research_detail_pages.index(newest_page) oldest_page_index = research_detail_pages.index(oldest_page) @@ -101,7 +106,9 @@ class TestResearchLibraryPage(research_test_base.ResearchHubTestCase): original_publication_date=research_test_utils.days_ago(1), ) - research_detail_pages = list(self.library_page._get_research_detail_pages(sort=constants.SORT_OLDEST_FIRST)) + research_detail_pages = list( + self.library_page.get_sorted_filtered_detail_pages(sort=constants.SORT_OLDEST_FIRST) + ) newest_page_index = research_detail_pages.index(newest_page) oldest_page_index = research_detail_pages.index(oldest_page) @@ -117,7 +124,9 @@ class TestResearchLibraryPage(research_test_base.ResearchHubTestCase): title="Banana", ) - research_detail_pages = list(self.library_page._get_research_detail_pages(sort=constants.SORT_ALPHABETICAL)) + research_detail_pages = list( + self.library_page.get_sorted_filtered_detail_pages(sort=constants.SORT_ALPHABETICAL) + ) apple_page_index = research_detail_pages.index(apple_page) banana_page_index = research_detail_pages.index(banana_page) @@ -135,7 +144,7 @@ class TestResearchLibraryPage(research_test_base.ResearchHubTestCase): ) research_detail_pages = list( - self.library_page._get_research_detail_pages(sort=constants.SORT_ALPHABETICAL_REVERSED) + self.library_page.get_sorted_filtered_detail_pages(sort=constants.SORT_ALPHABETICAL_REVERSED) ) apple_page_index = research_detail_pages.index(apple_page) @@ -153,9 +162,9 @@ class TestResearchLibraryPage(research_test_base.ResearchHubTestCase): original_publication_date=research_test_utils.days_ago(1), ) - default_sort_detail_pages = list(self.library_page._get_research_detail_pages()) + default_sort_detail_pages = list(self.library_page.get_sorted_filtered_detail_pages()) newest_first_detail_pages = list( - self.library_page._get_research_detail_pages(sort=constants.SORT_NEWEST_FIRST) + self.library_page.get_sorted_filtered_detail_pages(sort=constants.SORT_NEWEST_FIRST) ) self.assertEqual(default_sort_detail_pages, newest_first_detail_pages) @@ -166,7 +175,7 @@ class TestResearchLibraryPage(research_test_base.ResearchHubTestCase): for _ in range(6): detail_page_factory.ResearchDetailPageFactory(parent=self.library_page) - research_detail_pages = self.library_page._get_research_detail_pages() + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages() research_detail_pages_paginator = paginator.Paginator( object_list=research_detail_pages, @@ -200,7 +209,7 @@ class TestResearchLibraryPageSearch(TestResearchLibraryPage): collaborators="", ) - research_detail_pages = self.library_page._get_research_detail_pages(search="Apple") + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(research_detail_pages), 1) self.assertIn(apple_page, research_detail_pages) self.assertNotIn(banana_page, research_detail_pages) @@ -221,7 +230,7 @@ class TestResearchLibraryPageSearch(TestResearchLibraryPage): collaborators="", ) - research_detail_pages = self.library_page._get_research_detail_pages(search="Apple") + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(research_detail_pages), 1) self.assertIn(apple_page, research_detail_pages) @@ -243,7 +252,7 @@ class TestResearchLibraryPageSearch(TestResearchLibraryPage): collaborators="", ) - research_detail_pages = self.library_page._get_research_detail_pages(search="Apple") + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(research_detail_pages), 1) self.assertIn(apple_page, research_detail_pages) @@ -265,7 +274,7 @@ class TestResearchLibraryPageSearch(TestResearchLibraryPage): collaborators="Banana", ) - research_detail_pages = self.library_page._get_research_detail_pages(search="Apple") + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(research_detail_pages), 1) self.assertIn(apple_page, research_detail_pages) @@ -301,7 +310,7 @@ class TestResearchLibraryPageSearch(TestResearchLibraryPage): relations_factory.ResearchAuthorRelationFactory(detail_page=banana_page, author_profile=banana_profile) self.update_index() - research_detail_pages = self.library_page._get_research_detail_pages(search="Apple") + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(research_detail_pages), 1) self.assertIn(apple_page, research_detail_pages) @@ -335,7 +344,7 @@ class TestResearchLibraryPageSearch(TestResearchLibraryPage): relations_factory.ResearchDetailPageResearchTopicRelationFactory(detail_page=banana_page, topic=banana_topic) self.update_index() - research_detail_pages = self.library_page._get_research_detail_pages(search="Apple") + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(research_detail_pages), 1) self.assertIn(apple_page, research_detail_pages) @@ -365,7 +374,7 @@ class TestResearchLibraryPageSearch(TestResearchLibraryPage): ) self.update_index() - research_detail_pages = self.library_page._get_research_detail_pages(search="Apple") + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(search_query="Apple") self.assertEqual(len(research_detail_pages), 1) self.assertIn(apple_page, research_detail_pages) @@ -386,7 +395,8 @@ class TestResearchLibraryPageFilters(TestResearchLibraryPage): detail_page_2.authors.first().author_profile, ) - research_detail_pages = self.library_page._get_research_detail_pages(author_profile_ids=[author_profile.id]) + filter_form = ResearchLibraryPageFilterForm(data={"authors": [author_profile.id]}) + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_1, research_detail_pages) self.assertNotIn(detail_page_2, research_detail_pages) @@ -412,7 +422,7 @@ class TestResearchLibraryPageFilters(TestResearchLibraryPage): ) # Only show the page where both profiles are authors - research_detail_pages = response.context["research_detail_pages"] + research_detail_pages = response.context["detail_pages"] self.assertNotIn(detail_page_1, research_detail_pages) self.assertIn(detail_page_2, research_detail_pages) @@ -445,9 +455,8 @@ class TestResearchLibraryPageFilters(TestResearchLibraryPage): self.assertEqual(profile.translation_key, profile_fr.translation_key) translation.activate(self.fr_locale.language_code) - research_detail_pages = self.library_page.localized._get_research_detail_pages( - author_profile_ids=[profile_fr.id] - ) + filter_form = ResearchLibraryPageFilterForm(data={"authors": [profile_fr.id]}) + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_1_fr, research_detail_pages) self.assertIn(detail_page_2_fr, research_detail_pages) @@ -466,7 +475,8 @@ class TestResearchLibraryPageFilters(TestResearchLibraryPage): related_topics__topic=topic_B, ) - research_detail_pages = self.library_page._get_research_detail_pages(topic_ids=[topic_A.id]) + filter_form = ResearchLibraryPageFilterForm(data={"topics": [topic_A.id]}) + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_A, research_detail_pages) self.assertNotIn(detail_page_B, research_detail_pages) @@ -484,7 +494,8 @@ class TestResearchLibraryPageFilters(TestResearchLibraryPage): related_topics__topic=topic_A, ) - research_detail_pages = self.library_page._get_research_detail_pages(topic_ids=[topic_A.id, topic_B.id]) + filter_form = ResearchLibraryPageFilterForm(data={"topics": [topic_A.id, topic_B.id]}) + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_1, research_detail_pages) self.assertNotIn(detail_page_2, research_detail_pages) @@ -518,7 +529,8 @@ class TestResearchLibraryPageFilters(TestResearchLibraryPage): self.assertEqual(topic.translation_key, topic_fr.translation_key) translation.activate(self.fr_locale.language_code) - research_detail_pages = self.library_page.localized._get_research_detail_pages(topic_ids=[topic_fr.id]) + filter_form = ResearchLibraryPageFilterForm(data={"topics": [topic_fr.id]}) + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertEqual(len(research_detail_pages), 2) self.assertIn(detail_page_1_fr, research_detail_pages) @@ -538,7 +550,8 @@ class TestResearchLibraryPageFilters(TestResearchLibraryPage): related_regions__region=region_B, ) - research_detail_pages = self.library_page._get_research_detail_pages(region_ids=[region_A.id]) + filter_form = ResearchLibraryPageFilterForm(data={"regions": [region_A.id]}) + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_A, research_detail_pages) self.assertNotIn(detail_page_B, research_detail_pages) @@ -556,7 +569,8 @@ class TestResearchLibraryPageFilters(TestResearchLibraryPage): related_regions__region=region_A, ) - research_detail_pages = self.library_page._get_research_detail_pages(region_ids=[region_A.id, region_B.id]) + filter_form = ResearchLibraryPageFilterForm(data={"regions": [region_A.id, region_B.id]}) + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_1, research_detail_pages) self.assertNotIn(detail_page_2, research_detail_pages) @@ -590,7 +604,8 @@ class TestResearchLibraryPageFilters(TestResearchLibraryPage): self.assertEqual(region.translation_key, region_fr.translation_key) translation.activate(self.fr_locale.language_code) - research_detail_pages = self.library_page.localized._get_research_detail_pages(region_ids=[region_fr.id]) + filter_form = ResearchLibraryPageFilterForm(data={"regions": [region_fr.id]}) + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertEqual(len(research_detail_pages), 2) self.assertIn(detail_page_1_fr, research_detail_pages) @@ -608,7 +623,8 @@ class TestResearchLibraryPageFilters(TestResearchLibraryPage): original_publication_date=datetime.date(year_1 + 1, 6, 1), ) - research_detail_pages = self.library_page._get_research_detail_pages(year=year_1) + filter_form = ResearchLibraryPageFilterForm(data={"year": year_1}) + research_detail_pages = self.library_page.get_sorted_filtered_detail_pages(filter_form=filter_form) self.assertIn(detail_page_1, research_detail_pages) self.assertNotIn(detail_page_2, research_detail_pages) diff --git a/source/js/foundation/pages/research-hub-library.js b/source/js/foundation/pages/libraries-library-page.js similarity index 100% rename from source/js/foundation/pages/research-hub-library.js rename to source/js/foundation/pages/libraries-library-page.js