Skip to content

Commit b64d59f

Browse files
committed
MainMenu+M experimental
1 parent b21a5ee commit b64d59f

28 files changed

+1396
-33
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ docs/QueryAutoConfig.rst
159159
docs/TableAutoConfig.rst
160160
docs/views.rst
161161
docs/semantic_models.rst
162+
docs/M.rst
163+
docs/MainMenu.rst
164+
docs/main_menu.rst
162165

163166
# these are created by mutation testing
164167
docs/XXtest*

docs/_static/custom.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,9 @@ h2 {
100100
display: none;
101101
}
102102
}
103+
104+
.iommi_logo_menu {
105+
/*noinspection CssUnknownTarget*/
106+
background: url(https://docs.iommi.rocks/en/latest/_static/images/iommi_logo_text_only.svg) no-repeat;
107+
background-size: contain;
108+
}

docs/api.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ API Reference
2222
Fragment
2323
Header
2424
HeaderConfig
25+
M
26+
MainMenu
2527
Members
2628
Menu
2729
MenuItem

docs/test_doc_components.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
edit_tables
1111
forms
1212
fragments
13+
main_menu
1314
pages
1415
queries
1516
tables
1617
17-
1818
"""

docs/test_doc_main_menu.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from docs.models import Artist
2+
from iommi import Table
3+
from iommi.experimental.main_menu import (
4+
M,
5+
MainMenu,
6+
menu_access_control_middleware,
7+
)
8+
from tests.helpers import (
9+
req,
10+
show_output,
11+
)
12+
13+
# language=rst
14+
"""
15+
Main menu
16+
~~~~~~~~~
17+
18+
.. warning::
19+
20+
The `MainMenu` component is considered experimental. That means the API can change in breaking ways in minor releases of iommi. It also means you import from `iommi.experimental` to make that clear.
21+
22+
The main menu component in iommi is used to create the main navigation for your app. This is primarily useful for SaaS or internal apps. It creates a sidebar menu with support for nested menu items, and centralized access control that automatically shows only menu items the user has access to.
23+
24+
To set up your main menu you declare it, register the `iommi.experimental.main_menu.menu_access_control_middleware` middleware, and define the `IOMMI_MAIN_MENU` setting to point to where you have defined it (like `IOMMI_MAIN_MENU = 'your_app.main_menu.main_menu'`).
25+
26+
Access control is recursive, meaning that if a user does not have access to a menu item, it is automatically denied access to all subitems.
27+
"""
28+
29+
30+
def fake_view():
31+
pass # pragma: no cover
32+
33+
34+
menu_declaration = MainMenu(
35+
items=dict(
36+
artists=M(
37+
icon='user',
38+
view=fake_view,
39+
),
40+
albums=M(
41+
icon='compact-disc',
42+
view=fake_view,
43+
),
44+
),
45+
)
46+
47+
urlpatterns = menu_declaration.urlpatterns()
48+
49+
50+
def test_base(settings, medium_discography):
51+
# @test
52+
settings.IOMMI_MAIN_MENU = 'docs.test_doc_main_menu.menu_declaration'
53+
settings.ROOT_URLCONF = 'docs.test_doc_main_menu'
54+
assert 'iommi.experimental.main_menu.menu_access_control_middleware' in settings.MIDDLEWARE
55+
56+
response = menu_access_control_middleware(get_response=lambda request: Table(auto__model=Artist).bind(request=request).render_to_response())(req('get', url='/artists/'))
57+
show_output(response)
58+
# @end

examples/examples/apps.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from django.apps import AppConfig
22

3+
from iommi.path import register_path_decoding
4+
35

46
class ExampleConfig(AppConfig):
57
name = 'examples'
@@ -15,3 +17,5 @@ def ready(self):
1517

1618
register_search_fields(model=Artist, search_fields=['name'], allow_non_unique=True)
1719
register_search_fields(model=Album, search_fields=['name'], allow_non_unique=True)
20+
register_path_decoding(artist_pk=Artist)
21+
register_path_decoding(album_pk=Album)

examples/examples/main_menu.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
from examples.models import (
2+
Album,
3+
Artist,
4+
Track,
5+
)
6+
from iommi import Form
7+
from iommi.admin import Admin
8+
from iommi.experimental.main_menu import (
9+
EXTERNAL,
10+
M,
11+
MainMenu,
12+
)
13+
14+
import examples.views as views
15+
from examples import (
16+
experimental_examples,
17+
form_examples,
18+
menu_examples,
19+
page_examples,
20+
supernaut,
21+
table_examples,
22+
)
23+
24+
main_menu = MainMenu(
25+
items=dict(
26+
page=M(
27+
view=page_examples.IndexPage,
28+
items=dict(
29+
example_1=M(view=page_examples.HelloWorldPage),
30+
example_2=M(view=page_examples.page_view_example_2),
31+
example_3=M(view=page_examples.page_view_example_3),
32+
example_4=M(view=page_examples.page_view_example_4),
33+
live=M(view=page_examples.page_live),
34+
)
35+
),
36+
form=M(
37+
view=form_examples.IndexPage,
38+
items=dict(
39+
example_1=M(view=form_examples.form_example_1),
40+
example_2=M(view=form_examples.form_example_2),
41+
example_3=M(view=form_examples.form_example_3),
42+
example_4=M(view=form_examples.form_example_4),
43+
example_5=M(view=form_examples.form_example_5),
44+
example_6=M(view=form_examples.form_example_6),
45+
example_7=M(view=form_examples.form_example_7),
46+
example_8=M(view=form_examples.form_example_8),
47+
example_9=M(view=form_examples.form_example_error_messages),
48+
example_10=M(view=form_examples.form_example_children_that_are_not_fields),
49+
example_11=M(view=form_examples.form_example_children_that_are_not_fields_declarative),
50+
example_12=M(view=form_examples.form_example_nested_forms),
51+
example_13=M(view=form_examples.form_example_file_upload),
52+
example_14=M(view=form_examples.form_example_field_groups),
53+
example_15=M(view=form_examples.form_example_dependent_fields),
54+
all_fields=M(view=form_examples.all_field_sorts),
55+
),
56+
),
57+
table=M(
58+
view=table_examples.IndexPage,
59+
items=dict(
60+
all_columns=M(view=table_examples.all_column_sorts),
61+
example_1=M(view=table_examples.table_readme_example_1),
62+
example_2=M(view=table_examples.table_readme_example_2),
63+
example_3=M(view=table_examples.table_auto_example_1),
64+
example_4=M(view=table_examples.table_auto_example_2),
65+
example_5=M(view=table_examples.table_kitchen_sink),
66+
example_6=M(view=table_examples.example_6_view),
67+
example_7=M(view=table_examples.table_two),
68+
example_8=M(view=table_examples.table_post_handler_on_lists),
69+
example_9=M(view=table_examples.extra_fields),
70+
example_10=M(view=table_examples.csv),
71+
example_11=M(view=table_examples.edit_table),
72+
)
73+
),
74+
menu=M(
75+
view=menu_examples.menu_test,
76+
),
77+
supernaut=M(
78+
view=supernaut.IndexPage,
79+
items=dict(
80+
albums=M(
81+
view=supernaut.AlbumTable(auto__model=Album, columns__year__bulk__include=True),
82+
items=dict(
83+
create=M(view=Form.create(auto__model=Album)),
84+
album=M(
85+
path='<album_pk>/',
86+
params={'album'},
87+
view=supernaut.AlbumPage,
88+
display_name=lambda album, **_: album.name,
89+
url=lambda album, **_: album.get_absolute_url(),
90+
),
91+
),
92+
),
93+
artists=M(
94+
view=supernaut.ArtistTable(auto__model=Artist),
95+
items=dict(
96+
artist=M(
97+
path='<artist_pk>/',
98+
params={'artist'},
99+
view=supernaut.ArtistPage,
100+
display_name=lambda artist, **_: artist.name,
101+
url=lambda artist, **_: artist.get_absolute_url(),
102+
),
103+
),
104+
),
105+
tracks=M(view=supernaut.TrackTable(auto__model=Track)),
106+
),
107+
),
108+
iommi_admin=Admin.m(),
109+
login=M(
110+
view=EXTERNAL,
111+
display_name='Log in',
112+
url='/iommi-admin/login/?next=/',
113+
include=lambda request, **_: not request.user.is_authenticated,
114+
),
115+
log_out=M(
116+
view=EXTERNAL,
117+
display_name='Log out',
118+
url='/iommi-admin/logout/',
119+
include=lambda request, **_: request.user.is_authenticated,
120+
),
121+
),
122+
)
123+
124+
125+
# sitemap = []
126+
#
127+
# def generate_sitemap(urlpatterns, indent=0):
128+
# for p in urlpatterns:
129+
# sitemap.append(' ' * indent + (str(p.pattern) or "''") + ' - > ' + str(p.callback.__name__ if p.callback else None))
130+
# if hasattr(p, 'url_patterns'):
131+
# generate_sitemap(p.url_patterns, indent + 1)
132+
#
133+
# generate_sitemap(main_menu.urlpatterns())
134+
# print('!!!!')
135+
# print('\n'.join(sitemap))

examples/examples/models.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class Meta:
4242
verbose_name_plural = _('artists')
4343

4444
def get_absolute_url(self):
45-
return f'/supernaut/artist/{self}/'
45+
return f'/supernaut/artists/{self.pk}/'
4646

4747

4848
class Album(models.Model):
@@ -59,7 +59,7 @@ class Meta:
5959
verbose_name_plural = _('albums')
6060

6161
def get_absolute_url(self):
62-
return f'/supernaut/artist/{self.artist}/{self}/'
62+
return f'/supernaut/albums/{self.pk}/'
6363

6464

6565
class Track(models.Model):

examples/examples/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
'django.contrib.messages.middleware.MessageMiddleware',
8888
'django.middleware.clickjacking.XFrameOptionsMiddleware',
8989
'iommi.middleware',
90+
'iommi.experimental.main_menu.menu_access_control_middleware',
9091
]
9192

9293

@@ -124,6 +125,7 @@
124125

125126
STATIC_URL = '/static/'
126127
IOMMI_DEBUG = True
128+
IOMMI_MAIN_MENU = 'examples.main_menu.main_menu'
127129

128130
STATIC_ROOT = Path(__file__).parent.joinpath('static')
129131

examples/examples/supernaut.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,6 @@
2929
register_path_decoding(album=lambda string, **_: Album.objects.get(name=string))
3030

3131

32-
# Menu -----------------------------
33-
34-
35-
class SupernautMenu(Menu):
36-
home = MenuItem(url='/supernaut/', display_name=_('Home'))
37-
artists = MenuItem(url='/supernaut/artists/', display_name=_('Artists'))
38-
albums = MenuItem(url='/supernaut/albums/', display_name=_('Albums'))
39-
tracks = MenuItem(url='/supernaut/tracks/', display_name=_('Tracks'))
40-
41-
class Meta:
42-
attrs__class = {'fixed-top': True}
43-
44-
4532
# Tables ---------------------------
4633

4734

@@ -88,8 +75,6 @@ def columns__name__cell__url(row, **_):
8875

8976

9077
class IndexPage(Page):
91-
menu = SupernautMenu()
92-
9378
title = html.h1(_('Supernaut'))
9479
welcome_text = html.div(_('This is a discography of the best acts in music!'))
9580

0 commit comments

Comments
 (0)