+ {% with is_syllabus_property=True %}
+ {% for page in pages %}
+ {% with reverse_id=page.reverse_id %}
+ {% show_placeholder "additional_information" reverse_id %}
+ {% endwith %}
+ {% endfor %}
+ {% endwith %}
+
+ {% endif %}
+ {% endblock category_additional_information %}
+
{% block licenses %}
{% if current_page.publisher_is_draft or not current_page|is_empty_placeholder:"course_license_content" or not current_page|is_empty_placeholder:"course_license_participation" %}
diff --git a/src/richie/apps/courses/templatetags/extra_tags.py b/src/richie/apps/courses/templatetags/extra_tags.py
index 550952729c..cd0cff4e28 100644
--- a/src/richie/apps/courses/templatetags/extra_tags.py
+++ b/src/richie/apps/courses/templatetags/extra_tags.py
@@ -13,6 +13,7 @@
from classytags.arguments import Argument, MultiValueArgument
from classytags.core import Options, Tag
from classytags.utils import flatten_context
+from cms.api import Page
from cms.templatetags.cms_tags import (
Placeholder,
PlaceholderOptions,
@@ -23,6 +24,8 @@
from cms.utils.plugins import get_plugins
from richie.apps.courses.defaults import RICHIE_MAX_ARCHIVED_COURSE_RUNS
+from richie.apps.courses.models.category import Category
+from richie.apps.courses.models.course import Course
from ..lms import LMSHandler
from ..models import CourseRunCatalogVisibility
@@ -271,6 +274,36 @@ def joanie_product_widget_props(context):
return json.dumps({"productId": product_id, "courseCode": course_code})
+@register.simple_tag()
+def get_categories_pages_additional_information(course: Course) -> list[Page]:
+ """
+ Return categories pages have additional information and also have a page id.
+
+ usage: `{% get_categories_pages_additional_information current_page.course as pages %}`
+ """
+
+ categories_pages: list[Category] = course.get_categories()
+ categories_pages = categories_pages.filter(
+ extended_object__reverse_id__isnull=False
+ )
+
+ categories_with_information: list[Category] = []
+ for category in categories_pages:
+ additional_information = category.extended_object.get_placeholders().get(
+ slot="additional_information"
+ )
+ plugins = additional_information.get_plugins()
+
+ if len(plugins) > 0:
+ categories_with_information.append(category)
+
+ pages_have_additional_information: list[Page] = [
+ category.extended_object for category in categories_with_information
+ ]
+
+ return pages_have_additional_information
+
+
@register.simple_tag(takes_context=True)
def course_runs_list_widget_props(context):
"""
diff --git a/tests/apps/courses/test_templates_category_detail.py b/tests/apps/courses/test_templates_category_detail.py
index e67b927194..8a1243f5c7 100644
--- a/tests/apps/courses/test_templates_category_detail.py
+++ b/tests/apps/courses/test_templates_category_detail.py
@@ -21,6 +21,7 @@
OrganizationFactory,
PersonFactory,
)
+from richie.plugins.nesteditem.defaults import ACCORDION
class CategoryCMSTestCase(CMSTestCase):
@@ -553,3 +554,163 @@ def test_template_category_detail_meta_description_empty(self):
response,
'
CategoryFactory:
+ """
+ This method adds additional information to a category
+ """
+
+ placeholder = component.extended_object.placeholders.get(
+ slot="additional_information"
+ )
+
+ section = add_plugin(
+ language="en",
+ placeholder=placeholder,
+ plugin_type="SectionPlugin",
+ title="Additional Information",
+ )
+
+ container = add_plugin(
+ language="en",
+ placeholder=placeholder,
+ plugin_type="NestedItemPlugin",
+ variant=ACCORDION,
+ target=section,
+ )
+
+ for question in range(1, 3):
+ question_container = add_plugin(
+ language="en",
+ placeholder=placeholder,
+ plugin_type="NestedItemPlugin",
+ target=container,
+ content=f"{question}. question?",
+ variant=ACCORDION,
+ )
+
+ add_plugin(
+ language="en",
+ placeholder=placeholder,
+ plugin_type="NestedItemPlugin",
+ target=question_container,
+ content=f"Answer of question {question}.",
+ variant=ACCORDION,
+ )
+
+ return component
+
+ @transaction.atomic
+ def test_get_categories_pages_additional_information_filled_list_with_lookup(self):
+ """
+ This test validates when a course has categories with additional information and
+ the page_lookup `reverse_id` set the custom tag
+ `get_categories_pages_additional_information` must return a list
+ with the corresponding categories pages
+ """
+
+ category1 = CategoryFactory.create(page_title="Accessible", should_publish=True)
+ category2 = CategoryFactory.create(
+ page_title="Earth and universe sciences", should_publish=True
+ )
+ icon1 = CategoryFactory.create(
+ page_title="Available on edX.org", should_publish=True
+ )
+ icon2 = CategoryFactory.create(
+ page_title="Payment promotion", should_publish=True
+ )
+
+ for component in [category1, category2, icon1, icon2]:
+ reverse_id = component.extended_object.get_title().lower().replace(" ", "-")
+ component.extended_object.reverse_id = reverse_id
+ component.extended_object.save()
+
+ for component in [category1, icon1]:
+ component = self._add_info(component)
+
+ course: Course = CourseFactory.create(
+ fill_categories=[category1, category2],
+ fill_icons=[icon1, icon2],
+ )
+
+ all_categories = course.get_categories()
+ categories_pages_have_info = get_categories_pages_additional_information(course)
+
+ self.assertTrue(len(all_categories) == 4)
+ self.assertTrue(isinstance(categories_pages_have_info, list))
+ self.assertTrue(len(categories_pages_have_info) == 2)
+
+ for page in categories_pages_have_info:
+ self.assertTrue(page.get_title() in ["Accessible", "Available on edX.org"])
+ plugins = (
+ page.get_placeholders().get(slot="additional_information").get_plugins()
+ )
+ self.assertTrue(len(plugins) > 0)
+
+ self.assertTrue(plugins[0].plugin_type == "SectionPlugin")
+
+ for plugin in plugins[1:]:
+ self.assertTrue(plugin.plugin_type == "NestedItemPlugin")
+
+ @transaction.atomic
+ def test_get_categories_pages_additional_information_empty_list_with_lookup(self):
+ """
+ This test validates when a course does not have categories with additional information
+ but with the page_lookup `reverse_id` set the custom tag
+ `get_categories_pages_additional_information` must return an empty
+ """
+
+ category1 = CategoryFactory.create(page_title="Accessible", should_publish=True)
+ category2 = CategoryFactory.create(
+ page_title="Earth and universe sciences", should_publish=True
+ )
+ icon1 = CategoryFactory.create(
+ page_title="Available on edX.org", should_publish=True
+ )
+ icon2 = CategoryFactory.create(
+ page_title="Payment promotion", should_publish=True
+ )
+
+ for component in [category1, category2, icon1, icon2]:
+ reverse_id = component.extended_object.get_title().lower().replace(" ", "-")
+ component.extended_object.reverse_id = reverse_id
+ component.extended_object.save()
+
+ course: Course = CourseFactory.create(
+ fill_categories=[category1, category2],
+ fill_icons=[icon1, icon2],
+ )
+
+ all_categories = course.get_categories()
+ categories_pages_have_info = get_categories_pages_additional_information(course)
+
+ self.assertTrue(len(all_categories) == 4)
+ self.assertTrue(isinstance(categories_pages_have_info, list))
+ self.assertTrue(len(categories_pages_have_info) == 0)
+
+ for category in all_categories:
+ page = category.extended_object
+ plugins = (
+ page.get_placeholders().get(slot="additional_information").get_plugins()
+ )
+ self.assertTrue(len(plugins) == 0)
+
+ @transaction.atomic
+ def test_get_categories_pages_additional_information_filled_list_no_lookup(self):
+ """
+ This test validates when a course has categories with additional information but
+ without page_lookup `reverse_id` the custom tag
+ `get_categories_pages_additional_information` must return an empty list
+ """
+
+ category1 = CategoryFactory.create(page_title="Accessible", should_publish=True)
+ category2 = CategoryFactory.create(
+ page_title="Earth and universe sciences", should_publish=True
+ )
+ icon1 = CategoryFactory.create(
+ page_title="Available on edX.org", should_publish=True
+ )
+ icon2 = CategoryFactory.create(
+ page_title="Payment promotion", should_publish=True
+ )
+
+ for component in [category1, icon1]:
+ component = self._add_info(component)
+
+ course: Course = CourseFactory.create(
+ fill_categories=[category1, category2],
+ fill_icons=[icon1, icon2],
+ )
+
+ all_categories = course.get_categories()
+ categories_pages_have_info = get_categories_pages_additional_information(course)
+
+ self.assertTrue(len(all_categories) == 4)
+
+ for category in all_categories:
+ page = category.extended_object
+ self.assertTrue(page.reverse_id is None)
+
+ categories_have_info = [
+ category
+ for category in all_categories
+ if len(
+ category.extended_object.get_placeholders()
+ .get(slot="additional_information")
+ .get_plugins()
+ )
+ > 0
+ ]
+ self.assertTrue(len(categories_have_info) == 2)
+
+ for category in categories_have_info:
+ page = category.extended_object
+ self.assertTrue(page.get_title() in ["Accessible", "Available on edX.org"])
+ plugins = (
+ page.get_placeholders().get(slot="additional_information").get_plugins()
+ )
+
+ self.assertTrue(plugins[0].plugin_type == "SectionPlugin")
+
+ for plugin in plugins[1:]:
+ self.assertTrue(plugin.plugin_type == "NestedItemPlugin")
+
+ self.assertTrue(isinstance(categories_pages_have_info, list))
+ self.assertTrue(len(categories_pages_have_info) == 0)
+
+ @transaction.atomic
+ def test_get_categories_pages_additional_information_filled_list_content(self):
+ """
+ This test validates when a course has categories with additional information it
+ will insert to the content page their information
+ """
+
+ category1 = CategoryFactory.create(page_title="Accessible", should_publish=True)
+ category2 = CategoryFactory.create(
+ page_title="Earth and universe sciences", should_publish=True
+ )
+ icon1 = CategoryFactory.create(
+ page_title="Available on edX.org", should_publish=True
+ )
+ icon2 = CategoryFactory.create(
+ page_title="Payment promotion", should_publish=True
+ )
+
+ for component in [category1, category2, icon1, icon2]:
+ reverse_id = component.extended_object.get_title().lower().replace(" ", "-")
+ component.extended_object.reverse_id = reverse_id
+ component.extended_object.save()
+
+ for component in [category1, icon1]:
+ component = self._add_info(component)
+ component.get_page().publish("en")
+
+ course: Course = CourseFactory.create(
+ fill_categories=[category1, category2],
+ fill_icons=[icon1, icon2],
+ )
+
+ all_categories = course.get_categories()
+ categories_pages_have_info = get_categories_pages_additional_information(course)
+
+ self.assertTrue(len(all_categories) == 4)
+ self.assertTrue(isinstance(categories_pages_have_info, list))
+ self.assertTrue(len(categories_pages_have_info) == 2)
+
+ page = course.get_page()
+ page.publish("en")
+ url = page.get_absolute_url()
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(
+ response,
+ '
',
+ )
+
+ for question in range(1, 3):
+ self.assertContains(
+ response, f'
'
+ )
+ self.assertContains(
+ response,
+ f'
',
+ )
+
+ @transaction.atomic
+ def test_get_categories_pages_additional_information_content_no_lookup(self):
+ """
+ This test validates when a course has categories with additional information but
+ without `reverse_id` it will not insert the section title nor the
+ section block
+ """
+
+ category1 = CategoryFactory.create(page_title="Accessible", should_publish=True)
+ category2 = CategoryFactory.create(
+ page_title="Earth and universe sciences", should_publish=True
+ )
+ icon1 = CategoryFactory.create(
+ page_title="Available on edX.org", should_publish=True
+ )
+ icon2 = CategoryFactory.create(
+ page_title="Payment promotion", should_publish=True
+ )
+
+ for component in [category1, icon1]:
+ component = self._add_info(component)
+ component.get_page().publish("en")
+
+ course: Course = CourseFactory.create(
+ fill_categories=[category1, category2],
+ fill_icons=[icon1, icon2],
+ )
+
+ page = course.get_page()
+ page.publish("en")
+ url = page.get_absolute_url()
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertNotContains(
+ response,
+ '
',
+ )
+
+ for question in range(1, 3):
+ self.assertNotContains(
+ response, f'
'
+ )
+ self.assertNotContains(
+ response,
+ f'
',
+ )
+
+ @transaction.atomic
+ def test_get_categories_pages_additional_information_no_content_with_lookup(self):
+ """
+ This test validates when a course does not have categories with additional information but
+ with `reverse_id` it will not insert the section title nor the section block
+ """
+
+ category1 = CategoryFactory.create(page_title="Accessible", should_publish=True)
+ category2 = CategoryFactory.create(
+ page_title="Earth and universe sciences", should_publish=True
+ )
+ icon1 = CategoryFactory.create(
+ page_title="Available on edX.org", should_publish=True
+ )
+ icon2 = CategoryFactory.create(
+ page_title="Payment promotion", should_publish=True
+ )
+
+ for component in [category1, category2, icon1, icon2]:
+ reverse_id = component.extended_object.get_title().lower().replace(" ", "-")
+ component.extended_object.reverse_id = reverse_id
+ component.extended_object.save()
+ component.get_page().publish("en")
+
+ course: Course = CourseFactory.create(
+ fill_categories=[category1, category2],
+ fill_icons=[icon1, icon2],
+ )
+
+ page = course.get_page()
+ page.publish("en")
+ url = page.get_absolute_url()
+ response = self.client.get(url)
+
+ self.assertEqual(response.status_code, 200)
+ self.assertNotContains(
+ response,
+ '
',
+ )
+
+ for question in range(1, 3):
+ self.assertNotContains(
+ response, f''
+ )
+ self.assertNotContains(
+ response,
+ f'',
+ )