Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨(backend) Added new page extension 'IndexPage' #2493

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Versioning](https://semver.org/spec/v2.0.0.html).

### Added

- Added new page extension `MainMenuEntry`.
- Grid options for the Section plugin
- Make user waive its right of withdrawal when purchasing
a course product relation with `is_withdrawable` set to `false`
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 @@ -598,6 +598,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
15 changes: 14 additions & 1 deletion src/frontend/scss/components/_header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,18 @@
@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)};
}
// WARNING: Those are currently some variant samples during development
&--primary {
--r--menu--item--hover--color: #{$primary};
}
&--warning {
--r--menu--item--hover--color: #{$warning};
}

& > a {
@include sv-flex(1, 0, 100%);
display: flex;
Expand All @@ -228,6 +240,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 +249,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