Skip to content

Commit

Permalink
✨(backend) added new page extension 'IndexPage'
Browse files Browse the repository at this point in the history
This new extension cover the need of some options for pages in main
menu. These options are:

- Enable to show a dropdown of children page;
- Add a custom color class for the page on hover event in menu;

This implementation brings a new Page extension, new admin, new menu
modifier, new extension toolbar, new settings, menu template changes
and some CSS adjustments.
  • Loading branch information
sveetch committed Dec 3, 2024
1 parent 9cac139 commit 2af82b4
Show file tree
Hide file tree
Showing 17 changed files with 515 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Versioning](https://semver.org/spec/v2.0.0.html).

- Add Additional Information section for a category and
use them in a course page
- Added new page extension `MainMenuEntry`

## [2.33.0] - 2024-12-02

Expand Down
35 changes: 35 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,41 @@ $ make migrate

## Unreleased

- Header menu template has been modified to include features from `MainMenuEntry`
- There is a new setting that can be used to define a list of color variant for the menu
entry of a page:

```
RICHIE_MENU_ENTRY_COLOR_CLASSES = (
("", _("None")),
("red", _("Red")),
)
```

Selected choice value will be used as a variant name in a classname prefixed with
`topbar__item--`, so here for the *Red* it would be `topbar__item--red`. However it
depends from your menu template.

- There is a new setting that defines if admin can create a new PageIndex extension
for a page:

```
RICHIE_MAINMENUENTRY_ALLOW_CREATION = False
```

When migrating to this release you would probably to turn it on until you added the
extension to your main pages then it turn it off

- There is a new setting that defines the allowed page level that can hold
a PageIndex extension:

```
RICHIE_MAINMENUENTRY_MENU_ALLOWED_LEVEL = 0
```

Other page levels won't be allowed to create a PageIndex extension. Commonly it would
be `0` for when your main menu list the root pages.

## 2.31.0 to 2.33.0

- Add new `slider` plugin theme scheme:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,13 @@ class Base(StyleguideMixin, DRFMixin, RichieCoursesConfigurationMixin, Configura
"body > svg, #main-menu, .body-footer, .body-mentions"
)

# Wheither you can create PageIndex extension on page through toolbar if true or
# just editing existing extension if false
RICHIE_MAINMENUENTRY_ALLOW_CREATION = False

# Define which node level can be processed to search for pageindex extension
RICHIE_MAINMENUENTRY_MENU_ALLOWED_LEVEL = 0

# pylint: disable=invalid-name
@property
def ENVIRONMENT(self):
Expand Down
7 changes: 7 additions & 0 deletions sandbox/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,13 @@ class Base(StyleguideMixin, DRFMixin, RichieCoursesConfigurationMixin, Configura
environ_prefix=None,
)

# Wheither you can create PageIndex extension on page through toolbar if true or
# just editing existing extension if false
RICHIE_MAINMENUENTRY_ALLOW_CREATION = False

# Define which node level can be processed to search for pageindex extension
RICHIE_MAINMENUENTRY_MENU_ALLOWED_LEVEL = 0

@classmethod
def _get_environment(cls):
"""Environment in which the application is launched."""
Expand Down
8 changes: 7 additions & 1 deletion src/frontend/scss/components/_header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@
@include sv-flex(0, 0, auto);
}

// Define color variable for default item color on hover then available variants
@if r-theme-val(topbar, item-hover-color) {
--r--menu--item--hover--color: #{r-theme-val(topbar, item-hover-color)};
}

& > a {
@include sv-flex(1, 0, 100%);
display: flex;
Expand All @@ -228,6 +233,7 @@
@include media-breakpoint-up($r-topbar-breakpoint) {
position: relative;

// If there is no default hover color we assume there is also no variant
@if r-theme-val(topbar, item-hover-color) {
&::after {
content: '';
Expand All @@ -236,7 +242,7 @@
left: 0;
right: 0;
height: 8px;
background-color: r-theme-val(topbar, item-hover-color);
background-color: var(--r--menu--item--hover--color);
border-top-left-radius: 0.2rem;
border-top-right-radius: 0.2rem;
}
Expand Down
26 changes: 15 additions & 11 deletions src/richie/apps/core/templates/menu/header_menu.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
{% load cms_tags %}{% spaceless %}
{% load menu_tags %}{% spaceless %}

{% for child in children %}
{% with children_slug=child.get_menu_title|slugify %}
<li class="topbar__item
{% with children_slug=child.get_menu_title|slugify menu_options=child.menu_extension %}
<li class="topbar__item dropdown
{% if child.selected %} topbar__item--selected{% endif %}
{% if child.ancestor %} topbar__item--ancestor{% endif %}
{% if child.sibling %} topbar__item--sibling{% endif %}
{% if child.descendant %} topbar__item--descendant{% endif %}">
<a href="{{ child.attr.redirect_url|default:child.get_absolute_url }}">{{ child.get_menu_title }}</a>
{% comment %}Dropdown menu are not yet implemented since there is no need about it for now{% endcomment %}
{% comment %}{% if child.children %}
<ul>
{% show_menu from_level to_level extra_inactive extra_active template "" "" child %}
</ul>
{% endif %}{% endcomment %}
{% if child.descendant %} topbar__item--descendant{% endif %}
{% if menu_options.menu_color %} topbar__item--{{ menu_options.menu_color }}{% endif %}">
<a href="{{ child.attr.redirect_url|default:child.get_absolute_url }}">
{{ child.get_menu_title }}
</a>
{% comment %}Dropdown menu for children are only for page with index page
extension with a specific option enabled{% endcomment %}
{% if menu_options.allow_submenu and child.children %}
<ul>
{% show_menu from_level to_level extra_inactive extra_active template "" "" child %}
</ul>
{% endif %}
</li>
{% endwith %}
{% endfor %}
Expand Down
2 changes: 1 addition & 1 deletion src/richie/apps/core/templates/richie/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@

<nav class="topbar__menu">
<ul class="topbar__list">
{% show_menu 0 100 0 0 "menu/header_menu.html" %}
{% show_menu 0 100 100 100 "menu/header_menu.html" %}
</ul>
</nav>
</div>
Expand Down
16 changes: 16 additions & 0 deletions src/richie/apps/courses/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,21 @@ def snapshot(self, request, course_id, *args, **kwargs):
return JsonResponse({"id": new_page.course.id})


class MainMenuEntryAdmin(PageExtensionAdmin):
"""
Admin class for the MainMenuEntry model
"""

list_display = ["title", "allow_submenu"]

# pylint: disable=no-self-use
def title(self, obj):
"""
Get the page title from the related page
"""
return obj.extended_object.get_title()


class OrganizationAdmin(PageExtensionAdmin):
"""
Admin class for the Organization model
Expand Down Expand Up @@ -349,6 +364,7 @@ class LicenceAdmin(TranslatableAdmin):
admin.site.register(models.Course, CourseAdmin)
admin.site.register(models.CourseRun, CourseRunAdmin)
admin.site.register(models.Licence, LicenceAdmin)
admin.site.register(models.MainMenuEntry, MainMenuEntryAdmin)
admin.site.register(models.Organization, OrganizationAdmin)
admin.site.register(models.PageRole, PageRoleAdmin)
admin.site.register(models.Person, PersonAdmin)
79 changes: 79 additions & 0 deletions src/richie/apps/courses/cms_menus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
Menu modifier to add feature for menu template context.
"""

from django.conf import settings

from menus.base import Modifier
from menus.menu_pool import menu_pool

from .models import MainMenuEntry


class MenuWithMainMenuEntry(Modifier):
"""
Menu modifier to include MainMenuEntry extension data in menu template context.
In menu template you will be able to reach possible extension data from node
attribute ``menu_extension``. If node page has no extension it will have an empty
dict. Only a specific node level is processed and nodes with a different level
won't have the attribute ``menu_extension`` at all.
"""

# pylint: disable=too-many-arguments,too-many-positional-arguments
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
"""
Patch navigation nodes to include data from possible extension
``MainMenuEntry``.
For performance:
* This does not work for breadcrumb navigation (all extension options are mean
for menu only);
* This works only on the menu top level, it means the one defined as first
argument from tag ``{% show_menu .. %}``;
Then to avoid making a query for each node item to retrieve its possible
extension object, we get the extensions in bulk as values instead of objects.
Finally we add the data on nodes so they can used from menu template.
"""
# We are not altering breadcrumb menu, this is only for navigation menu and
# only for the visible menu (not the whole processed tree)
if not nodes or breadcrumb or not post_cut:
return nodes

# Get the page ids to process, only for the allowed node level
page_ids = [
node.id
for node in nodes
if node.level == settings.RICHIE_MAINMENUENTRY_MENU_ALLOWED_LEVEL
]

# No need to continue if we don't have any valid node
if not page_ids:
return nodes

# We directly get the extensions from their related page id and serialized
# as a dict instead of model object
extension_queryset = MainMenuEntry.objects.filter(
extended_object_id__in=page_ids
).values("extended_object_id", "allow_submenu", "menu_color")

# Pack extensions data into proper structure
extension_datas = {
item["extended_object_id"]: {
"allow_submenu": item["allow_submenu"],
"menu_color": item["menu_color"],
}
for item in extension_queryset
}

# Attach each possible extension data to its relative node
for node in nodes:
node.menu_extension = extension_datas.get(node.id, {})

return nodes


menu_pool.register_modifier(MenuWithMainMenuEntry)
52 changes: 51 additions & 1 deletion src/richie/apps/courses/cms_toolbars.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
Toolbar extension for the courses application
"""

from django.conf import settings
from django.utils.text import capfirst
from django.utils.translation import gettext_lazy as _

Expand All @@ -12,7 +13,7 @@
from cms.utils.urlutils import admin_reverse

from .defaults import PAGE_EXTENSION_TOOLBAR_ITEM_POSITION
from .models import Category, Course, Organization, Person
from .models import Category, Course, MainMenuEntry, Organization, Person


class BaseExtensionToolbar(ExtensionToolbar):
Expand Down Expand Up @@ -131,3 +132,52 @@ class PersonExtensionToolbar(BaseExtensionToolbar):
"""

model = Person


@toolbar_pool.register
class MainMenuEntryExtensionToolbar(BaseExtensionToolbar):
"""
This extension class customizes the toolbar for the MainMenuEntry page extension.
"""

model = MainMenuEntry

def populate(self):
"""
Specific extension populate method.
This extension entry only appears in toolbar if page already have extension or
if setting ``RICHIE_MAINMENUENTRY_ALLOW_CREATION`` is true. Finally the page
level must also match the allowed level from setting
``RICHIE_MAINMENUENTRY_MENU_ALLOWED_LEVEL``.
"""
# always use draft if we have a page
self.page = get_page_draft(self.request.current_page)
if not self.page:
# Nothing to do
return

# setup the extension toolbar with permissions and sanity checks
page_menu = self._setup_extension_toolbar()

if user_can_change_page(user=self.request.user, page=self.page):
# Retrieves extension instance (if any) and toolbar URL
page_extension, admin_url = self.get_page_extension_admin()
# Get the page node level
level = self.page.node.get_depth() - 1
allowed = page_extension is not None or (
page_extension is None
and settings.RICHIE_MAINMENUENTRY_ALLOW_CREATION is True
)
if (
allowed
and level == settings.RICHIE_MAINMENUENTRY_MENU_ALLOWED_LEVEL
and admin_url
):
# Adds a toolbar item in position 0 (at the top of the menu)
page_menu.add_modal_item(
_("Main menu settings"),
url=admin_url,
disabled=not self.toolbar.edit_mode_active,
position=PAGE_EXTENSION_TOOLBAR_ITEM_POSITION,
)
12 changes: 12 additions & 0 deletions src/richie/apps/courses/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,10 @@
"reverse_id": "organizations",
"template": "courses/cms/organization_detail.html",
}
MENUENTRIES_PAGE = {
"reverse_id": None,
"template": "richie/single_column.html",
}
PERSONS_PAGE = {"reverse_id": "persons", "template": "courses/cms/person_detail.html"}
PROGRAMS_PAGE = {
"reverse_id": "programs",
Expand Down Expand Up @@ -382,3 +386,11 @@
# Maximum number of archived course runs displayed by default on course detail page.
# The additional runs can be viewed by clicking on `View more` link.
RICHIE_MAX_ARCHIVED_COURSE_RUNS = 10

# Define possible hover color that can be choosen for an MainMenuEntry and to apply on
# its menu item
MENU_ENTRY_COLOR_CLASSES = getattr(
settings,
"RICHIE_MENU_ENTRY_COLOR_CLASSES",
(("", _("None")),),
)
23 changes: 23 additions & 0 deletions src/richie/apps/courses/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -910,3 +910,26 @@ def fill_excerpt(self, create, extracted, **kwargs):
plugin_type="PlainTextPlugin",
body=text,
)


class MainMenuEntryFactory(BLDPageExtensionDjangoModelFactory):
"""
A factory to automatically generate random yet meaningful menu entry page extensions
and their related page in our tests.
"""

class Meta:
model = models.MainMenuEntry
exclude = [
"page_in_navigation",
"page_languages",
"page_parent",
"page_reverse_id",
"page_template",
"page_title",
]

# fields concerning the related page
page_template = models.MainMenuEntry.PAGE["template"]
allow_submenu = False
menu_color = ""
Loading

0 comments on commit 2af82b4

Please sign in to comment.