-
Notifications
You must be signed in to change notification settings - Fork 32
/
ext.py
210 lines (173 loc) · 7.1 KB
/
ext.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# Copyright (C) 2015-2018 CERN.
# Copyright (C) 2022 RERO.
# Copyright (C) 2023-2024 Graz University of Technology.
#
# Invenio is free software; you can redistribute it and/or modify it
# under the terms of the MIT License; see LICENSE file for more details.
"""Invenio-Admin Flask extension."""
from __future__ import absolute_import, print_function
import warnings
import importlib_metadata
from flask_admin import Admin, AdminIndexView
from flask_login import current_user
from flask_menu import current_menu
from invenio_db import db
from invenio_i18n import lazy_gettext as _
from invenio_theme.proxies import current_theme_icons
from werkzeug.utils import import_string
from . import config
from .proxies import current_admin
from .views import protected_adminview_factory
class _AdminState(object):
"""State for Invenio-Admin."""
def __init__(self, app, admin, permission_factory, view_class_factory):
"""Initialize state.
:param app: The Flask application.
:param admin: The Flask-Admin application.
:param permission_factory: The permission factory to restrict access.
:param view_class_factory: The view class factory to initialize them.
"""
# Create admin instance.
self.app = app
self.admin = admin
self.permission_factory = permission_factory
self.view_class_factory = view_class_factory
def register_view(self, view_class, *args, **kwargs):
"""Register an admin view on this admin instance.
:param view_class: The view class name passed to the view factory.
:param args: Positional arugments for view class.
:param kwargs: Keyword arguments to view class.
"""
protected_view_class = self.view_class_factory(view_class)
if "endpoint" not in kwargs:
kwargs["endpoint"] = view_class(*args, **kwargs).endpoint
self.admin.add_view(protected_view_class(*args, **kwargs))
def load_entry_point_group(self, entry_point_group):
"""Load administration interface from entry point group.
:param str entry_point_group: Name of the entry point group.
"""
for ep in set(importlib_metadata.entry_points(group=entry_point_group)):
admin_ep = dict(ep.load())
keys = tuple(k in admin_ep for k in ("model", "modelview", "view_class"))
if keys == (False, False, True):
self.register_view(
admin_ep.pop("view_class"),
*admin_ep.pop("args", []),
**admin_ep.pop("kwargs", {}),
)
elif keys == (True, True, False):
warnings.warn(
"Usage of model and modelview kwargs are deprecated in "
"favor of view_class, args and kwargs.",
PendingDeprecationWarning,
)
self.register_view(
admin_ep.pop("modelview"),
admin_ep.pop("model"),
admin_ep.pop("session", db.session),
**admin_ep,
)
else:
raise Exception(
"Admin entry point dictionary must contain "
'either "view_class" OR "model" and "modelview" keys.'
)
class InvenioAdmin(object):
"""Invenio-Admin extension."""
def __init__(self, app=None, **kwargs):
"""Invenio-Admin extension initialization.
:param app: The Flask application. (Default: ``None``)
:param kwargs: Passed to :meth:`init_app`.
"""
if app:
self._state = self.init_app(app, **kwargs)
def init_app(
self,
app,
entry_point_group="invenio_admin.views",
permission_factory=None,
view_class_factory=protected_adminview_factory,
index_view_class=AdminIndexView,
):
"""Flask application initialization.
:param app: The Flask application.
:param entry_point_group: Name of entry point group to load
views/models from. (Default: ``'invenio_admin.views'``)
:param permission_factory: Default permission factory to use when
protecting an admin view. (Default:
:func:`~.permissions.admin_permission_factory`)
:param view_class_factory: Factory for creating admin view classes on
the fly. Used to protect admin views with authentication and
authorization. (Default:
:func:`~.views.protected_adminview_factory`)
:param index_view_class: Specify administrative interface index page.
(Default: :class:`flask_admin.base.AdminIndexView`)
:param kwargs: Passed to :class:`flask_admin.base.Admin`.
:returns: Extension state.
"""
self.init_config(app)
default_permission_factory = app.config["ADMIN_PERMISSION_FACTORY"]
permission_factory = permission_factory or import_string(
default_permission_factory
)
# Create administration app.
admin = Admin(
app,
name=app.config["ADMIN_APPNAME"],
template_mode=app.config["ADMIN_TEMPLATE_MODE"],
index_view=view_class_factory(index_view_class)(),
)
# Create admin state
state = _AdminState(app, admin, permission_factory, view_class_factory)
if entry_point_group:
state.load_entry_point_group(entry_point_group)
app.extensions["invenio-admin"] = state
return state
@staticmethod
def init_config(app):
"""Initialize configuration.
:param app: The Flask application.
"""
# Set default configuration
for k in dir(config):
if k == "ADMIN_BASE_TEMPLATE" and getattr(config, k) is None:
continue
if k.startswith("ADMIN_"):
app.config.setdefault(k, getattr(config, k))
def __getattr__(self, name):
"""Proxy to state object.
:param name: Attribute name of the state.
"""
return getattr(self._state, name, None)
def finalize_app(app):
"""Finalize app."""
lazy_base_template(app)
init_menu(app)
def lazy_base_template(app):
"""Initialize admin base template lazily."""
base_template = app.config.get("ADMIN_BASE_TEMPLATE")
invenio_admin = app.extensions["invenio-admin"]
if base_template:
invenio_admin.admin.base_template = base_template
def init_menu(app):
"""Initialize menu before first request."""
# Register settings menu
current_menu.submenu("settings.admin").register(
"admin.index",
# NOTE: Menu item text (icon replaced by a cogs icon).
_(
"%(icon)s Administration",
icon=f'<i class="{current_theme_icons.cogs}"></i>',
), # noqa
visible_when=_has_admin_access,
order=100,
)
def _has_admin_access():
"""Function used to check if a user has any admin access."""
return (
current_user.is_authenticated
and current_admin.permission_factory(current_admin.admin.index_view).can()
)