richie 2.33.1.dev6__py2.py3-none-any.whl → 2.33.1.dev7__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of richie might be problematic. Click here for more details.
- frontend/scss/components/_header.scss +7 -1
- richie/apps/core/templates/menu/header_menu.html +15 -11
- richie/apps/core/templates/richie/base.html +1 -1
- richie/apps/courses/admin.py +16 -0
- richie/apps/courses/cms_menus.py +79 -0
- richie/apps/courses/cms_toolbars.py +51 -1
- richie/apps/courses/defaults.py +12 -0
- richie/apps/courses/factories.py +23 -0
- richie/apps/courses/migrations/0035_add_menuentry.py +74 -0
- richie/apps/courses/models/__init__.py +1 -0
- richie/apps/courses/models/menuentry.py +51 -0
- richie/apps/courses/settings/__init__.py +11 -0
- richie/static/richie/css/main.css +1 -1
- {richie-2.33.1.dev6.dist-info → richie-2.33.1.dev7.dist-info}/METADATA +1 -1
- {richie-2.33.1.dev6.dist-info → richie-2.33.1.dev7.dist-info}/RECORD +19 -16
- {richie-2.33.1.dev6.dist-info → richie-2.33.1.dev7.dist-info}/LICENSE +0 -0
- {richie-2.33.1.dev6.dist-info → richie-2.33.1.dev7.dist-info}/WHEEL +0 -0
- {richie-2.33.1.dev6.dist-info → richie-2.33.1.dev7.dist-info}/top_level.txt +0 -0
- {richie-2.33.1.dev6.dist-info → richie-2.33.1.dev7.dist-info}/zip-safe +0 -0
|
@@ -208,6 +208,11 @@
|
|
|
208
208
|
@include sv-flex(0, 0, auto);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// Define color variable for default item color on hover then available variants
|
|
212
|
+
@if r-theme-val(topbar, item-hover-color) {
|
|
213
|
+
--r--menu--item--hover--color: #{r-theme-val(topbar, item-hover-color)};
|
|
214
|
+
}
|
|
215
|
+
|
|
211
216
|
& > a {
|
|
212
217
|
@include sv-flex(1, 0, 100%);
|
|
213
218
|
display: flex;
|
|
@@ -228,6 +233,7 @@
|
|
|
228
233
|
@include media-breakpoint-up($r-topbar-breakpoint) {
|
|
229
234
|
position: relative;
|
|
230
235
|
|
|
236
|
+
// If there is no default hover color we assume there is also no variant
|
|
231
237
|
@if r-theme-val(topbar, item-hover-color) {
|
|
232
238
|
&::after {
|
|
233
239
|
content: '';
|
|
@@ -236,7 +242,7 @@
|
|
|
236
242
|
left: 0;
|
|
237
243
|
right: 0;
|
|
238
244
|
height: 8px;
|
|
239
|
-
background-color: r
|
|
245
|
+
background-color: var(--r--menu--item--hover--color);
|
|
240
246
|
border-top-left-radius: 0.2rem;
|
|
241
247
|
border-top-right-radius: 0.2rem;
|
|
242
248
|
}
|
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
{% load
|
|
1
|
+
{% load menu_tags %}{% spaceless %}
|
|
2
2
|
|
|
3
3
|
{% for child in children %}
|
|
4
|
-
{% with children_slug=child.get_menu_title|slugify %}
|
|
5
|
-
<li class="topbar__item
|
|
4
|
+
{% with children_slug=child.get_menu_title|slugify menu_options=child.menu_extension %}
|
|
5
|
+
<li class="topbar__item dropdown
|
|
6
6
|
{% if child.selected %} topbar__item--selected{% endif %}
|
|
7
7
|
{% if child.ancestor %} topbar__item--ancestor{% endif %}
|
|
8
8
|
{% if child.sibling %} topbar__item--sibling{% endif %}
|
|
9
|
-
{% if child.descendant %} topbar__item--descendant{% endif %}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
{%
|
|
9
|
+
{% if child.descendant %} topbar__item--descendant{% endif %}
|
|
10
|
+
{% if menu_options.menu_color %} topbar__item--{{ menu_options.menu_color }}{% endif %}">
|
|
11
|
+
<a href="{{ child.attr.redirect_url|default:child.get_absolute_url }}">
|
|
12
|
+
{{ child.get_menu_title }}
|
|
13
|
+
</a>
|
|
14
|
+
{% comment %}Dropdown menu for children are only for page with index page
|
|
15
|
+
extension with a specific option enabled{% endcomment %}
|
|
16
|
+
{% if menu_options.allow_submenu and child.children %}
|
|
17
|
+
<ul>
|
|
18
|
+
{% show_menu from_level to_level extra_inactive extra_active template "" "" child %}
|
|
19
|
+
</ul>
|
|
20
|
+
{% endif %}
|
|
17
21
|
</li>
|
|
18
22
|
{% endwith %}
|
|
19
23
|
{% endfor %}
|
richie/apps/courses/admin.py
CHANGED
|
@@ -280,6 +280,21 @@ class CourseAdmin(FrontendEditableAdminMixin, PageExtensionAdmin):
|
|
|
280
280
|
return JsonResponse({"id": new_page.course.id})
|
|
281
281
|
|
|
282
282
|
|
|
283
|
+
class MainMenuEntryAdmin(PageExtensionAdmin):
|
|
284
|
+
"""
|
|
285
|
+
Admin class for the MainMenuEntry model
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
list_display = ["title", "allow_submenu"]
|
|
289
|
+
|
|
290
|
+
# pylint: disable=no-self-use
|
|
291
|
+
def title(self, obj):
|
|
292
|
+
"""
|
|
293
|
+
Get the page title from the related page
|
|
294
|
+
"""
|
|
295
|
+
return obj.extended_object.get_title()
|
|
296
|
+
|
|
297
|
+
|
|
283
298
|
class OrganizationAdmin(PageExtensionAdmin):
|
|
284
299
|
"""
|
|
285
300
|
Admin class for the Organization model
|
|
@@ -349,6 +364,7 @@ admin.site.register(models.Category, CategoryAdmin)
|
|
|
349
364
|
admin.site.register(models.Course, CourseAdmin)
|
|
350
365
|
admin.site.register(models.CourseRun, CourseRunAdmin)
|
|
351
366
|
admin.site.register(models.Licence, LicenceAdmin)
|
|
367
|
+
admin.site.register(models.MainMenuEntry, MainMenuEntryAdmin)
|
|
352
368
|
admin.site.register(models.Organization, OrganizationAdmin)
|
|
353
369
|
admin.site.register(models.PageRole, PageRoleAdmin)
|
|
354
370
|
admin.site.register(models.Person, PersonAdmin)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Menu modifier to add feature for menu template context.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from django.conf import settings
|
|
6
|
+
|
|
7
|
+
from menus.base import Modifier
|
|
8
|
+
from menus.menu_pool import menu_pool
|
|
9
|
+
|
|
10
|
+
from .models import MainMenuEntry
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MenuWithMainMenuEntry(Modifier):
|
|
14
|
+
"""
|
|
15
|
+
Menu modifier to include MainMenuEntry extension data in menu template context.
|
|
16
|
+
|
|
17
|
+
In menu template you will be able to reach possible extension data from node
|
|
18
|
+
attribute ``menu_extension``. If node page has no extension it will have an empty
|
|
19
|
+
dict. Only a specific node level is processed and nodes with a different level
|
|
20
|
+
won't have the attribute ``menu_extension`` at all.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
24
|
+
def modify(self, request, nodes, namespace, root_id, post_cut, breadcrumb):
|
|
25
|
+
"""
|
|
26
|
+
Patch navigation nodes to include data from possible extension
|
|
27
|
+
``MainMenuEntry``.
|
|
28
|
+
|
|
29
|
+
For performance:
|
|
30
|
+
|
|
31
|
+
* This does not work for breadcrumb navigation (all extension options are mean
|
|
32
|
+
for menu only);
|
|
33
|
+
* This works only on the menu top level, it means the one defined as first
|
|
34
|
+
argument from tag ``{% show_menu .. %}``;
|
|
35
|
+
|
|
36
|
+
Then to avoid making a query for each node item to retrieve its possible
|
|
37
|
+
extension object, we get the extensions in bulk as values instead of objects.
|
|
38
|
+
|
|
39
|
+
Finally we add the data on nodes so they can used from menu template.
|
|
40
|
+
"""
|
|
41
|
+
# We are not altering breadcrumb menu, this is only for navigation menu and
|
|
42
|
+
# only for the visible menu (not the whole processed tree)
|
|
43
|
+
if not nodes or breadcrumb or not post_cut:
|
|
44
|
+
return nodes
|
|
45
|
+
|
|
46
|
+
# Get the page ids to process, only for the allowed node level
|
|
47
|
+
page_ids = [
|
|
48
|
+
node.id
|
|
49
|
+
for node in nodes
|
|
50
|
+
if node.level == settings.RICHIE_MAINMENUENTRY_MENU_ALLOWED_LEVEL
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# No need to continue if we don't have any valid node
|
|
54
|
+
if not page_ids:
|
|
55
|
+
return nodes
|
|
56
|
+
|
|
57
|
+
# We directly get the extensions from their related page id and serialized
|
|
58
|
+
# as a dict instead of model object
|
|
59
|
+
extension_queryset = MainMenuEntry.objects.filter(
|
|
60
|
+
extended_object_id__in=page_ids
|
|
61
|
+
).values("extended_object_id", "allow_submenu", "menu_color")
|
|
62
|
+
|
|
63
|
+
# Pack extensions data into proper structure
|
|
64
|
+
extension_datas = {
|
|
65
|
+
item["extended_object_id"]: {
|
|
66
|
+
"allow_submenu": item["allow_submenu"],
|
|
67
|
+
"menu_color": item["menu_color"],
|
|
68
|
+
}
|
|
69
|
+
for item in extension_queryset
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Attach each possible extension data to its relative node
|
|
73
|
+
for node in nodes:
|
|
74
|
+
node.menu_extension = extension_datas.get(node.id, {})
|
|
75
|
+
|
|
76
|
+
return nodes
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
menu_pool.register_modifier(MenuWithMainMenuEntry)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Toolbar extension for the courses application
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from django.conf import settings
|
|
5
6
|
from django.utils.text import capfirst
|
|
6
7
|
from django.utils.translation import gettext_lazy as _
|
|
7
8
|
|
|
@@ -12,7 +13,7 @@ from cms.utils.page_permissions import user_can_add_subpage, user_can_change_pag
|
|
|
12
13
|
from cms.utils.urlutils import admin_reverse
|
|
13
14
|
|
|
14
15
|
from .defaults import PAGE_EXTENSION_TOOLBAR_ITEM_POSITION
|
|
15
|
-
from .models import Category, Course, Organization, Person
|
|
16
|
+
from .models import Category, Course, MainMenuEntry, Organization, Person
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class BaseExtensionToolbar(ExtensionToolbar):
|
|
@@ -131,3 +132,52 @@ class PersonExtensionToolbar(BaseExtensionToolbar):
|
|
|
131
132
|
"""
|
|
132
133
|
|
|
133
134
|
model = Person
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@toolbar_pool.register
|
|
138
|
+
class MainMenuEntryExtensionToolbar(BaseExtensionToolbar):
|
|
139
|
+
"""
|
|
140
|
+
This extension class customizes the toolbar for the MainMenuEntry page extension.
|
|
141
|
+
"""
|
|
142
|
+
|
|
143
|
+
model = MainMenuEntry
|
|
144
|
+
|
|
145
|
+
def populate(self):
|
|
146
|
+
"""
|
|
147
|
+
Specific extension populate method.
|
|
148
|
+
|
|
149
|
+
This extension entry only appears in toolbar if page already have extension or
|
|
150
|
+
if setting ``RICHIE_MAINMENUENTRY_ALLOW_CREATION`` is true. Finally the page
|
|
151
|
+
level must also match the allowed level from setting
|
|
152
|
+
``RICHIE_MAINMENUENTRY_MENU_ALLOWED_LEVEL``.
|
|
153
|
+
"""
|
|
154
|
+
# always use draft if we have a page
|
|
155
|
+
self.page = get_page_draft(self.request.current_page)
|
|
156
|
+
if not self.page:
|
|
157
|
+
# Nothing to do
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# setup the extension toolbar with permissions and sanity checks
|
|
161
|
+
page_menu = self._setup_extension_toolbar()
|
|
162
|
+
|
|
163
|
+
if user_can_change_page(user=self.request.user, page=self.page):
|
|
164
|
+
# Retrieves extension instance (if any) and toolbar URL
|
|
165
|
+
page_extension, admin_url = self.get_page_extension_admin()
|
|
166
|
+
# Get the page node level
|
|
167
|
+
level = self.page.node.get_depth() - 1
|
|
168
|
+
allowed = page_extension is not None or (
|
|
169
|
+
page_extension is None
|
|
170
|
+
and settings.RICHIE_MAINMENUENTRY_ALLOW_CREATION is True
|
|
171
|
+
)
|
|
172
|
+
if (
|
|
173
|
+
allowed
|
|
174
|
+
and level == settings.RICHIE_MAINMENUENTRY_MENU_ALLOWED_LEVEL
|
|
175
|
+
and admin_url
|
|
176
|
+
):
|
|
177
|
+
# Adds a toolbar item in position 0 (at the top of the menu)
|
|
178
|
+
page_menu.add_modal_item(
|
|
179
|
+
_("Main menu settings"),
|
|
180
|
+
url=admin_url,
|
|
181
|
+
disabled=not self.toolbar.edit_mode_active,
|
|
182
|
+
position=PAGE_EXTENSION_TOOLBAR_ITEM_POSITION,
|
|
183
|
+
)
|
richie/apps/courses/defaults.py
CHANGED
|
@@ -312,6 +312,10 @@ ORGANIZATIONS_PAGE = {
|
|
|
312
312
|
"reverse_id": "organizations",
|
|
313
313
|
"template": "courses/cms/organization_detail.html",
|
|
314
314
|
}
|
|
315
|
+
MENUENTRIES_PAGE = {
|
|
316
|
+
"reverse_id": None,
|
|
317
|
+
"template": "richie/single_column.html",
|
|
318
|
+
}
|
|
315
319
|
PERSONS_PAGE = {"reverse_id": "persons", "template": "courses/cms/person_detail.html"}
|
|
316
320
|
PROGRAMS_PAGE = {
|
|
317
321
|
"reverse_id": "programs",
|
|
@@ -382,3 +386,11 @@ EFFORT_UNITS = {
|
|
|
382
386
|
# Maximum number of archived course runs displayed by default on course detail page.
|
|
383
387
|
# The additional runs can be viewed by clicking on `View more` link.
|
|
384
388
|
RICHIE_MAX_ARCHIVED_COURSE_RUNS = 10
|
|
389
|
+
|
|
390
|
+
# Define possible hover color that can be choosen for an MainMenuEntry and to apply on
|
|
391
|
+
# its menu item
|
|
392
|
+
MENU_ENTRY_COLOR_CLASSES = getattr(
|
|
393
|
+
settings,
|
|
394
|
+
"RICHIE_MENU_ENTRY_COLOR_CLASSES",
|
|
395
|
+
(("", _("None")),),
|
|
396
|
+
)
|
richie/apps/courses/factories.py
CHANGED
|
@@ -910,3 +910,26 @@ class ProgramFactory(PageExtensionDjangoModelFactory):
|
|
|
910
910
|
plugin_type="PlainTextPlugin",
|
|
911
911
|
body=text,
|
|
912
912
|
)
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
class MainMenuEntryFactory(BLDPageExtensionDjangoModelFactory):
|
|
916
|
+
"""
|
|
917
|
+
A factory to automatically generate random yet meaningful menu entry page extensions
|
|
918
|
+
and their related page in our tests.
|
|
919
|
+
"""
|
|
920
|
+
|
|
921
|
+
class Meta:
|
|
922
|
+
model = models.MainMenuEntry
|
|
923
|
+
exclude = [
|
|
924
|
+
"page_in_navigation",
|
|
925
|
+
"page_languages",
|
|
926
|
+
"page_parent",
|
|
927
|
+
"page_reverse_id",
|
|
928
|
+
"page_template",
|
|
929
|
+
"page_title",
|
|
930
|
+
]
|
|
931
|
+
|
|
932
|
+
# fields concerning the related page
|
|
933
|
+
page_template = models.MainMenuEntry.PAGE["template"]
|
|
934
|
+
allow_submenu = False
|
|
935
|
+
menu_color = ""
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Generated by Django 4.2.16 on 2024-09-19 00:24
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
from ..defaults import MENU_ENTRY_COLOR_CLASSES
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Migration(migrations.Migration):
|
|
10
|
+
|
|
11
|
+
dependencies = [
|
|
12
|
+
("cms", "0022_auto_20180620_1551"),
|
|
13
|
+
("courses", "0034_auto_20230817_1736"),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.CreateModel(
|
|
18
|
+
name="MainMenuEntry",
|
|
19
|
+
fields=[
|
|
20
|
+
(
|
|
21
|
+
"id",
|
|
22
|
+
models.AutoField(
|
|
23
|
+
auto_created=True,
|
|
24
|
+
primary_key=True,
|
|
25
|
+
serialize=False,
|
|
26
|
+
verbose_name="ID",
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
(
|
|
30
|
+
"allow_submenu",
|
|
31
|
+
models.BooleanField(
|
|
32
|
+
default=False,
|
|
33
|
+
help_text="If enabled the page entry in menu will be a dropdown for its possible children.",
|
|
34
|
+
verbose_name="Allow submenu",
|
|
35
|
+
),
|
|
36
|
+
),
|
|
37
|
+
(
|
|
38
|
+
"menu_color",
|
|
39
|
+
models.CharField(
|
|
40
|
+
blank=True,
|
|
41
|
+
choices=MENU_ENTRY_COLOR_CLASSES,
|
|
42
|
+
default="",
|
|
43
|
+
help_text="A color used to display page entry in menu.",
|
|
44
|
+
max_length=10,
|
|
45
|
+
verbose_name="Color in menu",
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
(
|
|
49
|
+
"extended_object",
|
|
50
|
+
models.OneToOneField(
|
|
51
|
+
editable=False,
|
|
52
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
53
|
+
to="cms.page",
|
|
54
|
+
),
|
|
55
|
+
),
|
|
56
|
+
(
|
|
57
|
+
"public_extension",
|
|
58
|
+
models.OneToOneField(
|
|
59
|
+
editable=False,
|
|
60
|
+
null=True,
|
|
61
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
62
|
+
related_name="draft_extension",
|
|
63
|
+
to="courses.mainmenuentry",
|
|
64
|
+
),
|
|
65
|
+
),
|
|
66
|
+
],
|
|
67
|
+
options={
|
|
68
|
+
"verbose_name": "main menu entry",
|
|
69
|
+
"verbose_name_plural": "main menu entries",
|
|
70
|
+
"db_table": "richie_menuentry",
|
|
71
|
+
"ordering": ["-pk"],
|
|
72
|
+
},
|
|
73
|
+
),
|
|
74
|
+
]
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Declare and configure the models for the menu entry part
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from django.db import models
|
|
6
|
+
from django.utils.translation import gettext_lazy as _
|
|
7
|
+
|
|
8
|
+
from cms.extensions.extension_pool import extension_pool
|
|
9
|
+
|
|
10
|
+
from ...core.models import BasePageExtension
|
|
11
|
+
from .. import defaults
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MainMenuEntry(BasePageExtension):
|
|
15
|
+
"""
|
|
16
|
+
The MainMenuEntry extension defines some options for a page entry in the main menu.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
PAGE = defaults.MENUENTRIES_PAGE
|
|
20
|
+
|
|
21
|
+
class Meta:
|
|
22
|
+
db_table = "richie_menuentry"
|
|
23
|
+
ordering = ["-pk"]
|
|
24
|
+
verbose_name = _("main menu entry")
|
|
25
|
+
verbose_name_plural = _("main menu entries")
|
|
26
|
+
|
|
27
|
+
def __str__(self):
|
|
28
|
+
"""Human representation of an main menu entry page"""
|
|
29
|
+
model = self._meta.verbose_name.title()
|
|
30
|
+
name = self.extended_object.get_title()
|
|
31
|
+
return f"{model:s}: {name:s}"
|
|
32
|
+
|
|
33
|
+
allow_submenu = models.BooleanField(
|
|
34
|
+
_("Allow submenu"),
|
|
35
|
+
default=False,
|
|
36
|
+
help_text=_(
|
|
37
|
+
"If enabled the page entry in menu will be a dropdown for its possible "
|
|
38
|
+
"children."
|
|
39
|
+
),
|
|
40
|
+
)
|
|
41
|
+
menu_color = models.CharField(
|
|
42
|
+
_("Color in menu"),
|
|
43
|
+
max_length=10,
|
|
44
|
+
default="",
|
|
45
|
+
blank=True,
|
|
46
|
+
choices=defaults.MENU_ENTRY_COLOR_CLASSES,
|
|
47
|
+
help_text=_("A color used to display page entry in menu."),
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
extension_pool.register(MainMenuEntry)
|
|
@@ -597,3 +597,14 @@ RICHIE_SIMPLEPICTURE_PRESETS = {
|
|
|
597
597
|
"sizes": "60px",
|
|
598
598
|
},
|
|
599
599
|
}
|
|
600
|
+
|
|
601
|
+
# If true the toolbar item will already be showed. If false only a page which already
|
|
602
|
+
# have the extension will have the toolbar item and users won't be able to add
|
|
603
|
+
# MainMenuEntry extension on existing page, only create new page with index extension
|
|
604
|
+
# through the wizard.
|
|
605
|
+
RICHIE_MAINMENUENTRY_ALLOW_CREATION = False
|
|
606
|
+
|
|
607
|
+
# Define which node level can be processed to search for MainMenuEntry extension. You
|
|
608
|
+
# can set it to 'None' for never processing any node.
|
|
609
|
+
# This is a limit against performance issues to avoid making querysets for nothing.
|
|
610
|
+
RICHIE_MAINMENUENTRY_MENU_ALLOWED_LEVEL = 0
|