general: add category-based management access (forms/cards/workflows) (#21991)
gitea-wip/wcs/pipeline/head Build started...
Details
gitea-wip/wcs/pipeline/head Build started...
Details
This commit is contained in:
parent
dada06c191
commit
b4ee66cbcf
|
@ -1,6 +1,8 @@
|
|||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import pytest
|
||||
from webtest import Upload
|
||||
|
||||
from wcs import fields
|
||||
from wcs.admin.settings import UserFieldsFormDef
|
||||
|
@ -560,3 +562,77 @@ def test_card_management_view(pub):
|
|||
app = login(get_app(pub))
|
||||
resp = app.get('/backoffice/cards/1/')
|
||||
assert 'backoffice/data/foo/' in resp
|
||||
|
||||
|
||||
def test_card_category_management_roles(pub, backoffice_user, backoffice_role):
|
||||
app = login(get_app(pub), username='backoffice-user', password='backoffice-user')
|
||||
app.get('/backoffice/cards/', status=403)
|
||||
|
||||
CardDefCategory.wipe()
|
||||
cat = CardDefCategory(name='Foo')
|
||||
cat.store()
|
||||
|
||||
CardDef.wipe()
|
||||
carddef = CardDef()
|
||||
carddef.name = 'card title'
|
||||
carddef.category_id = cat.id
|
||||
carddef.fields = []
|
||||
carddef.store()
|
||||
|
||||
cat = CardDefCategory(name='Bar')
|
||||
cat.management_roles = [backoffice_role]
|
||||
cat.store()
|
||||
|
||||
resp = app.get('/backoffice/cards/')
|
||||
assert 'Foo' not in resp.text # not a category managed by user
|
||||
assert 'card title' not in resp.text # carddef in that category
|
||||
assert 'Bar' not in resp.text # not yet any form in this category
|
||||
|
||||
resp = resp.click('New Card')
|
||||
resp.forms[0]['name'] = 'card in category'
|
||||
assert len(resp.forms[0]['category_id'].options) == 1 # single option
|
||||
assert resp.forms[0]['category_id'].value == cat.id # the category managed by user
|
||||
resp = resp.forms[0].submit().follow()
|
||||
new_carddef = CardDef.get_by_urlname('card-in-category')
|
||||
|
||||
# check category select only let choose one
|
||||
resp = resp.click(href='/category')
|
||||
assert len(resp.forms[0]['category_id'].options) == 1 # single option
|
||||
assert resp.forms[0]['category_id'].value == cat.id # the category managed by user
|
||||
|
||||
resp = app.get('/backoffice/cards/')
|
||||
assert 'Bar' in resp.text # now there's a form in this category
|
||||
assert 'card in category' in resp.text
|
||||
|
||||
# no access to subdirectories
|
||||
assert 'href="categories/"' not in resp.text
|
||||
app.get('/backoffice/cards/categories/', status=403)
|
||||
|
||||
# no import into other category
|
||||
carddef_xml = ET.tostring(carddef.export_to_xml(include_id=True))
|
||||
resp = resp.click(href='import')
|
||||
resp.forms[0]['file'] = Upload('carddef.wcs', carddef_xml)
|
||||
resp = resp.forms[0].submit()
|
||||
assert 'Invalid File (unauthorized category)' in resp.text
|
||||
|
||||
# check access to inspect page
|
||||
carddef.workflow_roles = {'_viewer': str(backoffice_role.id)}
|
||||
carddef.store()
|
||||
|
||||
carddata = carddef.data_class()()
|
||||
carddata.just_created()
|
||||
carddata.store()
|
||||
|
||||
resp = app.get(carddata.get_backoffice_url())
|
||||
assert 'inspect' not in resp.text
|
||||
resp = app.get(carddata.get_backoffice_url() + 'inspect', status=403)
|
||||
|
||||
new_carddef.workflow_roles = {'_viewer': str(backoffice_role.id)}
|
||||
new_carddef.store()
|
||||
|
||||
carddata = new_carddef.data_class()()
|
||||
carddata.just_created()
|
||||
carddata.store()
|
||||
resp = app.get(carddata.get_backoffice_url())
|
||||
assert 'inspect' in resp.text
|
||||
resp = app.get(carddata.get_backoffice_url() + 'inspect')
|
||||
|
|
|
@ -2698,3 +2698,83 @@ def test_form_new_computed_field(pub):
|
|||
assert FormDef.get(1).fields[0].key == 'computed'
|
||||
assert FormDef.get(1).fields[0].label == 'foobar'
|
||||
assert FormDef.get(1).fields[0].varname == 'foobar'
|
||||
|
||||
|
||||
def test_form_category_management_roles(pub, backoffice_user, backoffice_role):
|
||||
app = login(get_app(pub), username='backoffice-user', password='backoffice-user')
|
||||
app.get('/backoffice/forms/', status=403)
|
||||
|
||||
Category.wipe()
|
||||
cat = Category(name='Foo')
|
||||
cat.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.category_id = cat.id
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
|
||||
cat = Category(name='Bar')
|
||||
cat.management_roles = [backoffice_role]
|
||||
cat.store()
|
||||
|
||||
resp = app.get('/backoffice/forms/')
|
||||
assert 'Foo' not in resp.text # not a category managed by user
|
||||
assert 'form title' not in resp.text # formdef in that category
|
||||
assert 'Bar' not in resp.text # not yet any form in this category
|
||||
|
||||
app.get('/backoffice/forms/%s/' % formdef.id, status=403)
|
||||
|
||||
resp = resp.click('New Form')
|
||||
resp.forms[0]['name'] = 'form in category'
|
||||
assert len(resp.forms[0]['category_id'].options) == 1 # single option
|
||||
assert resp.forms[0]['category_id'].value == cat.id # the category managed by user
|
||||
resp = resp.forms[0].submit().follow()
|
||||
new_formdef = FormDef.get_by_urlname('form-in-category')
|
||||
|
||||
# check category select only let choose one
|
||||
resp = resp.click(href='/category')
|
||||
assert len(resp.forms[0]['category_id'].options) == 1 # single option
|
||||
assert resp.forms[0]['category_id'].value == cat.id # the category managed by user
|
||||
|
||||
resp = app.get('/backoffice/forms/')
|
||||
assert 'Bar' in resp.text # now there's a form in this category
|
||||
assert 'form in category' in resp.text
|
||||
|
||||
# no access to subdirectories
|
||||
assert 'href="categories/"' not in resp.text
|
||||
assert 'href="data-sources/"' not in resp.text
|
||||
assert 'href="blocks/"' not in resp.text
|
||||
app.get('/backoffice/forms/categories/', status=403)
|
||||
app.get('/backoffice/forms/data-sources/', status=403)
|
||||
app.get('/backoffice/forms/blocks/', status=403)
|
||||
|
||||
# no import into other category
|
||||
formdef_xml = ET.tostring(formdef.export_to_xml(include_id=True))
|
||||
resp = resp.click(href='import')
|
||||
resp.forms[0]['file'] = Upload('formdef.wcs', formdef_xml)
|
||||
resp = resp.forms[0].submit()
|
||||
assert 'Invalid File (unauthorized category)' in resp.text
|
||||
|
||||
# check access to inspect page
|
||||
formdef.workflow_roles = {'_receiver': int(backoffice_role.id)}
|
||||
formdef.store()
|
||||
|
||||
formdata = formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
|
||||
resp = app.get(formdata.get_backoffice_url())
|
||||
assert 'inspect' not in resp.text
|
||||
resp = app.get(formdata.get_backoffice_url() + 'inspect', status=403)
|
||||
|
||||
new_formdef.workflow_roles = {'_receiver': int(backoffice_role.id)}
|
||||
new_formdef.store()
|
||||
|
||||
formdata = new_formdef.data_class()()
|
||||
formdata.just_created()
|
||||
formdata.store()
|
||||
resp = app.get(formdata.get_backoffice_url())
|
||||
assert 'inspect' in resp.text
|
||||
resp = app.get(formdata.get_backoffice_url() + 'inspect')
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import io
|
||||
import os
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
@ -2824,3 +2825,68 @@ def test_workflows_categories_in_index(pub):
|
|||
resp = app.get('/backoffice/workflows/')
|
||||
assert 'Uncategorised' in resp.text
|
||||
assert 'XcategoryY' in resp.text
|
||||
|
||||
|
||||
def test_workflow_category_management_roles(pub, backoffice_user, backoffice_role):
|
||||
app = login(get_app(pub), username='backoffice-user', password='backoffice-user')
|
||||
app.get('/backoffice/workflows/', status=403)
|
||||
|
||||
WorkflowCategory.wipe()
|
||||
cat = WorkflowCategory(name='Foo')
|
||||
cat.store()
|
||||
|
||||
Workflow.wipe()
|
||||
workflow = Workflow()
|
||||
workflow.name = 'workflow title'
|
||||
workflow.category_id = cat.id
|
||||
workflow.store()
|
||||
|
||||
cat = WorkflowCategory(name='Bar')
|
||||
cat.management_roles = [backoffice_role]
|
||||
cat.store()
|
||||
|
||||
resp = app.get('/backoffice/workflows/')
|
||||
assert 'Foo' not in resp.text # not a category managed by user
|
||||
assert 'workflow title' not in resp.text # workflow in that category
|
||||
assert 'Bar' not in resp.text # not yet any form in this category
|
||||
|
||||
resp = resp.click('New Workflow')
|
||||
resp.forms[0]['name'] = 'workflow in category'
|
||||
assert len(resp.forms[0]['category_id'].options) == 1 # single option
|
||||
assert resp.forms[0]['category_id'].value == cat.id # the category managed by user
|
||||
resp = resp.forms[0].submit().follow()
|
||||
|
||||
# check category select only let choose one
|
||||
resp = resp.click(href='category')
|
||||
assert len(resp.forms[0]['category_id'].options) == 1 # single option
|
||||
assert resp.forms[0]['category_id'].value == cat.id # the category managed by user
|
||||
|
||||
resp = app.get('/backoffice/workflows/')
|
||||
assert 'Bar' in resp.text # now there's a form in this category
|
||||
assert 'workflow in category' in resp.text
|
||||
|
||||
# no access to subdirectories
|
||||
assert 'href="categories/"' not in resp.text
|
||||
assert 'href="data-sources/"' not in resp.text
|
||||
assert 'href="mail-templates/"' not in resp.text
|
||||
app.get('/backoffice/workflows/categories/', status=403)
|
||||
app.get('/backoffice/workflows/data-sources/', status=403)
|
||||
app.get('/backoffice/workflows/mail-templates/', status=403)
|
||||
|
||||
# no import into other category
|
||||
workflow_xml = ET.tostring(workflow.export_to_xml(include_id=True))
|
||||
resp = resp.click(href='import')
|
||||
resp.forms[0]['file'] = Upload('workflow.wcs', workflow_xml)
|
||||
resp = resp.forms[0].submit()
|
||||
assert 'Invalid File (unauthorized category)' in resp.text
|
||||
|
||||
# access to default workflows
|
||||
app.get('/backoffice/workflows/_carddef_default/')
|
||||
resp = app.get('/backoffice/workflows/_default/')
|
||||
|
||||
# duplicate on default workflows should open a dialog
|
||||
resp = resp.click(href='duplicate')
|
||||
assert len(resp.forms[0]['category_id'].options) == 1 # single option
|
||||
resp = resp.forms[0].submit('cancel').follow()
|
||||
resp = resp.click(href='duplicate')
|
||||
resp = resp.forms[0].submit('submit').follow()
|
||||
|
|
|
@ -4,6 +4,8 @@ from unittest import mock
|
|||
|
||||
import pytest
|
||||
|
||||
from wcs.qommon.ident.password_accounts import PasswordAccount
|
||||
|
||||
from .utilities import EmailsMocking, HttpRequestsMocking, SMSMocking
|
||||
|
||||
|
||||
|
@ -115,3 +117,33 @@ def sql_queries(monkeypatch):
|
|||
monkeypatch.setattr(psycopg2, 'connect', connect)
|
||||
yield queries
|
||||
wcs.sql.cleanup_connection()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def backoffice_role(pub):
|
||||
role = pub.role_class.get_on_index('backoffice-role', 'slug', ignore_errors=True)
|
||||
if not role:
|
||||
role = pub.role_class(name='backoffice role')
|
||||
role.allows_backoffice_access = True
|
||||
role.store()
|
||||
assert role.slug == 'backoffice-role'
|
||||
return role
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def backoffice_user(pub, backoffice_role):
|
||||
try:
|
||||
user = pub.user_class.get_users_with_email('backoffice-user@example.net')[0]
|
||||
except IndexError:
|
||||
user = pub.user_class()
|
||||
user.name = 'backoffice user'
|
||||
user.email = 'backoffice-user@example.net'
|
||||
user.roles = [backoffice_role.id]
|
||||
user.store()
|
||||
|
||||
account1 = PasswordAccount(id='backoffice-user')
|
||||
account1.set_password('backoffice-user')
|
||||
account1.user_id = user.id
|
||||
account1.store()
|
||||
|
||||
return user
|
||||
|
|
|
@ -24,7 +24,7 @@ from wcs.backoffice.snapshots import SnapshotsDirectory
|
|||
from wcs.blocks import BlockDef, BlockdefImportError
|
||||
from wcs.qommon import _, misc, template
|
||||
from wcs.qommon.backoffice.menu import html_top
|
||||
from wcs.qommon.errors import TraversalError
|
||||
from wcs.qommon.errors import AccessForbiddenError, TraversalError
|
||||
from wcs.qommon.form import FileWidget, Form, HtmlWidget, StringWidget
|
||||
|
||||
|
||||
|
@ -195,6 +195,8 @@ class BlocksDirectory(Directory):
|
|||
self.section = section
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if not get_publisher().get_backoffice_root().is_global_accessible('forms'):
|
||||
raise AccessForbiddenError()
|
||||
get_response().breadcrumb.append(('blocks/', _('Fields Blocks')))
|
||||
return super()._q_traverse(path)
|
||||
|
||||
|
|
|
@ -23,17 +23,21 @@ from wcs.categories import CardDefCategory, Category, WorkflowCategory
|
|||
from wcs.formdef import FormDef
|
||||
from wcs.qommon import _, misc, template
|
||||
from wcs.qommon.backoffice.menu import html_top
|
||||
from wcs.qommon.errors import AccessForbiddenError
|
||||
from wcs.qommon.form import Form, HtmlWidget, SingleSelectWidget, StringWidget, WidgetList, WysiwygTextWidget
|
||||
from wcs.workflows import Workflow
|
||||
|
||||
|
||||
def get_categories(category_class):
|
||||
t = sorted((misc.simplify(x.name), x.id, x.name, x.id) for x in category_class.select())
|
||||
def get_categories(category_class, filter_function):
|
||||
t = sorted(
|
||||
(misc.simplify(x.name), x.id, x.name, x.id) for x in category_class.select() if filter_function(x)
|
||||
)
|
||||
return [x[1:] for x in t]
|
||||
|
||||
|
||||
class CategoryUI:
|
||||
category_class = Category
|
||||
management_roles_hint_text = _('Roles allowed to create, edit and delete forms.')
|
||||
|
||||
def __init__(self, category):
|
||||
self.category = category
|
||||
|
@ -66,6 +70,21 @@ class CategoryUI:
|
|||
if not new:
|
||||
# include permission fields
|
||||
roles = list(get_publisher().role_class.select(order_by='name'))
|
||||
if 'management_roles' in [x[0] for x in self.category_class.XML_NODES]:
|
||||
form.add(
|
||||
WidgetList,
|
||||
'management_roles',
|
||||
title=_('Management Roles'),
|
||||
element_type=SingleSelectWidget,
|
||||
value=self.category.management_roles,
|
||||
add_element_label=_('Add Role'),
|
||||
element_kwargs={
|
||||
'render_br': False,
|
||||
'options': [(None, '---', None)]
|
||||
+ [(x, x.name, x.id) for x in roles if not x.is_internal()],
|
||||
},
|
||||
hint=self.management_roles_hint_text,
|
||||
)
|
||||
if 'export_roles' in [x[0] for x in self.category_class.XML_NODES]:
|
||||
form.add(
|
||||
WidgetList,
|
||||
|
@ -79,7 +98,7 @@ class CategoryUI:
|
|||
'options': [(None, '---', None)]
|
||||
+ [(x, x.name, x.id) for x in roles if not x.is_internal()],
|
||||
},
|
||||
hint=_('Roles allowed to export data'),
|
||||
hint=_('Roles allowed to export data.'),
|
||||
)
|
||||
if 'statistics_roles' in [x[0] for x in self.category_class.XML_NODES]:
|
||||
form.add(
|
||||
|
@ -94,7 +113,7 @@ class CategoryUI:
|
|||
'options': [(None, '---', None)]
|
||||
+ [(x, x.name, x.id) for x in roles if not x.is_internal()],
|
||||
},
|
||||
hint=_('Roles with access to the statistics page'),
|
||||
hint=_('Roles with access to the statistics page.'),
|
||||
)
|
||||
|
||||
form.add_submit('submit', _('Submit'))
|
||||
|
@ -110,7 +129,13 @@ class CategoryUI:
|
|||
form.get_widget('name').set_error(_('This name is already used'))
|
||||
raise ValueError()
|
||||
|
||||
for attribute in ('description', 'redirect_url', 'export_roles', 'statistics_roles'):
|
||||
for attribute in (
|
||||
'description',
|
||||
'redirect_url',
|
||||
'management_roles',
|
||||
'export_roles',
|
||||
'statistics_roles',
|
||||
):
|
||||
widget = form.get_widget(attribute)
|
||||
if widget:
|
||||
setattr(self.category, attribute, widget.parse())
|
||||
|
@ -120,10 +145,12 @@ class CategoryUI:
|
|||
|
||||
class CardDefCategoryUI(CategoryUI):
|
||||
category_class = CardDefCategory
|
||||
management_roles_hint_text = _('Roles allowed to create, edit and delete card models.')
|
||||
|
||||
|
||||
class WorkflowCategoryUI(CategoryUI):
|
||||
category_class = WorkflowCategory
|
||||
management_roles_hint_text = _('Roles allowed to create, edit and delete workflows.')
|
||||
|
||||
|
||||
class CategoryPage(Directory):
|
||||
|
@ -234,6 +261,8 @@ class WorkflowCategoryPage(CategoryPage):
|
|||
|
||||
class CategoriesDirectory(Directory):
|
||||
_q_exports = ['', 'new', 'update_order']
|
||||
|
||||
base_section = 'forms'
|
||||
category_class = Category
|
||||
category_ui_class = CategoryUI
|
||||
category_page_class = CategoryPage
|
||||
|
@ -301,11 +330,14 @@ class CategoriesDirectory(Directory):
|
|||
return self.category_page_class(component)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if not get_publisher().get_backoffice_root().is_global_accessible(self.base_section):
|
||||
raise AccessForbiddenError()
|
||||
get_response().breadcrumb.append(('categories/', _('Categories')))
|
||||
return super()._q_traverse(path)
|
||||
|
||||
|
||||
class CardDefCategoriesDirectory(CategoriesDirectory):
|
||||
base_section = 'cards'
|
||||
category_class = CardDefCategory
|
||||
category_ui_class = CardDefCategoryUI
|
||||
category_page_class = CardDefCategoryPage
|
||||
|
@ -313,6 +345,7 @@ class CardDefCategoriesDirectory(CategoriesDirectory):
|
|||
|
||||
|
||||
class WorkflowCategoriesDirectory(CategoriesDirectory):
|
||||
base_section = 'workflows'
|
||||
category_class = WorkflowCategory
|
||||
category_ui_class = WorkflowCategoryUI
|
||||
category_page_class = WorkflowCategoryPage
|
||||
|
|
|
@ -31,6 +31,7 @@ from wcs.data_sources import (
|
|||
from wcs.formdef import get_formdefs_of_all_kinds
|
||||
from wcs.qommon import _, errors, misc, template
|
||||
from wcs.qommon.backoffice.menu import html_top
|
||||
from wcs.qommon.errors import AccessForbiddenError
|
||||
from wcs.qommon.form import (
|
||||
CheckboxWidget,
|
||||
DurationWidget,
|
||||
|
@ -421,6 +422,12 @@ class NamedDataSourcesDirectory(Directory):
|
|||
]
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if (
|
||||
not get_publisher().get_backoffice_root().is_global_accessible('forms')
|
||||
and not get_publisher().get_backoffice_root().is_global_accessible('workflows')
|
||||
and not get_publisher().get_backoffice_root().is_global_accessible('cards')
|
||||
):
|
||||
raise AccessForbiddenError()
|
||||
get_response().breadcrumb.append(('data-sources/', _('Data Sources')))
|
||||
return super()._q_traverse(path)
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ from wcs.forms.root import qrcode
|
|||
from wcs.qommon import _, force_str, get_logger, misc, template
|
||||
from wcs.qommon.afterjobs import AfterJob
|
||||
from wcs.qommon.backoffice.menu import html_top
|
||||
from wcs.qommon.errors import TraversalError
|
||||
from wcs.qommon.errors import AccessForbiddenError, TraversalError
|
||||
from wcs.qommon.form import (
|
||||
CheckboxesWidget,
|
||||
CheckboxWidget,
|
||||
|
@ -62,15 +62,29 @@ from .fields import FieldDefPage, FieldsDirectory
|
|||
from .logged_errors import LoggedErrorsDirectory
|
||||
|
||||
|
||||
def is_global_accessible(section):
|
||||
return get_publisher().get_backoffice_root().is_global_accessible(section)
|
||||
|
||||
|
||||
class FormDefUI:
|
||||
formdef_class = FormDef
|
||||
category_class = Category
|
||||
section = 'forms'
|
||||
|
||||
def __init__(self, formdef):
|
||||
self.formdef = formdef
|
||||
|
||||
def get_categories(self):
|
||||
return get_categories(self.category_class)
|
||||
global_access = is_global_accessible(self.section)
|
||||
user_roles = set(get_request().user.get_roles())
|
||||
|
||||
def filter_function(category):
|
||||
if global_access:
|
||||
return True
|
||||
management_roles = {x.id for x in getattr(category, 'management_roles') or []}
|
||||
return bool(user_roles.intersection(management_roles))
|
||||
|
||||
return get_categories(self.category_class, filter_function=filter_function)
|
||||
|
||||
@classmethod
|
||||
def get_workflows(cls, condition=lambda x: True):
|
||||
|
@ -87,12 +101,14 @@ class FormDefUI:
|
|||
form.add(StringWidget, 'name', title=_('Name'), required=True, size=40, value=formdef.name)
|
||||
categories = self.get_categories()
|
||||
if categories:
|
||||
if is_global_accessible(self.section):
|
||||
categories = [(None, '---', '')] + list(categories)
|
||||
form.add(
|
||||
SingleSelectWidget,
|
||||
'category_id',
|
||||
title=_('Category'),
|
||||
value=formdef.category_id,
|
||||
options=[(None, '---', '')] + categories,
|
||||
options=categories,
|
||||
)
|
||||
workflows = self.get_workflows()
|
||||
if len(workflows) > 1:
|
||||
|
@ -182,6 +198,8 @@ class AdminFieldsDirectory(FieldsDirectory):
|
|||
class OptionsDirectory(Directory):
|
||||
category_class = Category
|
||||
category_empty_choice = _('Select a category for this form')
|
||||
section = 'forms'
|
||||
|
||||
_q_exports = [
|
||||
'confirmation',
|
||||
'only_allow_one',
|
||||
|
@ -199,9 +217,10 @@ class OptionsDirectory(Directory):
|
|||
'user_support',
|
||||
]
|
||||
|
||||
def __init__(self, formdef):
|
||||
def __init__(self, formdef, formdefui):
|
||||
self.formdef = formdef
|
||||
self.changed = False
|
||||
self.formdefui = formdefui
|
||||
|
||||
def confirmation(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
|
@ -331,7 +350,9 @@ class OptionsDirectory(Directory):
|
|||
return self.handle(form, _('Keywords'))
|
||||
|
||||
def category(self):
|
||||
categories = get_categories(self.category_class)
|
||||
categories = self.formdefui.get_categories()
|
||||
if is_global_accessible(self.section):
|
||||
categories = [(None, '---', '')] + list(categories)
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.widgets.append(HtmlWidget('<p>%s</p>' % self.category_empty_choice))
|
||||
form.add(
|
||||
|
@ -339,7 +360,7 @@ class OptionsDirectory(Directory):
|
|||
'category_id',
|
||||
title=_('Category'),
|
||||
value=self.formdef.category_id,
|
||||
options=[(None, '---', '')] + categories,
|
||||
options=list(categories),
|
||||
)
|
||||
return self.handle(form, _('Category'))
|
||||
|
||||
|
@ -572,6 +593,7 @@ class FormDefPage(Directory):
|
|||
formdef_export_prefix = 'form'
|
||||
formdef_ui_class = FormDefUI
|
||||
formdef_default_workflow = '_default'
|
||||
section = 'forms'
|
||||
options_directory_class = OptionsDirectory
|
||||
|
||||
delete_message = _('You are about to irrevocably delete this form.')
|
||||
|
@ -595,14 +617,14 @@ class FormDefPage(Directory):
|
|||
self.fields.html_top = self.html_top
|
||||
self.role = WorkflowRoleDirectory(self.formdef)
|
||||
self.role.html_top = self.html_top
|
||||
self.options = self.options_directory_class(self.formdef)
|
||||
self.options = self.options_directory_class(self.formdef, self.formdefui)
|
||||
self.logged_errors_dir = LoggedErrorsDirectory(
|
||||
parent_dir=self, formdef_class=self.formdef_class, formdef_id=self.formdef.id
|
||||
)
|
||||
self.snapshots_dir = SnapshotsDirectory(self.formdef)
|
||||
|
||||
def html_top(self, title):
|
||||
return html_top('forms', title)
|
||||
return html_top(self.section, title)
|
||||
|
||||
def add_option_line(self, link, label, current_value, popup=True):
|
||||
return htmltext(
|
||||
|
@ -1681,6 +1703,7 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
formdef_page_class = FormDefPage
|
||||
formdef_ui_class = FormDefUI
|
||||
|
||||
section = 'forms'
|
||||
top_title = _('Forms')
|
||||
import_title = _('Import Form')
|
||||
import_submit_label = _('Import Form')
|
||||
|
@ -1696,23 +1719,43 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
)
|
||||
|
||||
def html_top(self, title):
|
||||
return html_top('forms', title)
|
||||
return html_top(self.section, title)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append(('forms/', _('Forms')))
|
||||
get_response().breadcrumb.append(('%s/' % self.section, self.top_title))
|
||||
return super()._q_traverse(path)
|
||||
|
||||
def is_accessible(self, user):
|
||||
if is_global_accessible(self.section):
|
||||
return True
|
||||
|
||||
# check for access to specific categories
|
||||
user_roles = set(user.get_roles())
|
||||
for category in self.category_class.select():
|
||||
management_roles = {x.id for x in getattr(category, 'management_roles') or []}
|
||||
if management_roles and user_roles.intersection(management_roles):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _q_index(self):
|
||||
self.html_top(title=self.top_title)
|
||||
r = TemplateIO(html=True)
|
||||
get_response().add_javascript(['jquery.js', 'widget_list.js'])
|
||||
r += self.form_actions()
|
||||
|
||||
global_access = is_global_accessible(self.section)
|
||||
|
||||
cats = self.category_class.select()
|
||||
self.category_class.sort_by_position(cats)
|
||||
one = False
|
||||
formdefs = self.formdef_class.select(order_by='name', ignore_errors=True, lightweight=True)
|
||||
for c in cats:
|
||||
if not global_access:
|
||||
user_roles = set(get_request().user.get_roles())
|
||||
management_roles = {x.id for x in getattr(c, 'management_roles') or []}
|
||||
if not user_roles.intersection(management_roles):
|
||||
continue
|
||||
l2 = [x for x in formdefs if str(x.category_id) == str(c.id)]
|
||||
l2 = [x for x in l2 if not x.disabled or (x.disabled and x.disabled_redirection)] + [
|
||||
x for x in l2 if x.disabled and not x.disabled_redirection
|
||||
|
@ -1721,16 +1764,17 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
r += self.form_list(l2, title=c.name)
|
||||
one = True
|
||||
|
||||
l2 = [x for x in formdefs if not x.category]
|
||||
if l2:
|
||||
if one:
|
||||
title = _('Misc')
|
||||
else:
|
||||
title = None
|
||||
l2 = [x for x in l2 if not x.disabled or (x.disabled and x.disabled_redirection)] + [
|
||||
x for x in l2 if x.disabled and not x.disabled_redirection
|
||||
]
|
||||
r += self.form_list(l2, title=title)
|
||||
if global_access:
|
||||
l2 = [x for x in formdefs if not x.category]
|
||||
if l2:
|
||||
if one:
|
||||
title = _('Misc')
|
||||
else:
|
||||
title = None
|
||||
l2 = [x for x in l2 if not x.disabled or (x.disabled and x.disabled_redirection)] + [
|
||||
x for x in l2 if x.disabled and not x.disabled_redirection
|
||||
]
|
||||
r += self.form_list(l2, title=title)
|
||||
return r.getvalue()
|
||||
|
||||
def form_actions(self):
|
||||
|
@ -1740,11 +1784,12 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
r += htmltext('<h2>%s</h2>') % _('Forms')
|
||||
if has_roles:
|
||||
r += htmltext('<span class="actions">')
|
||||
r += htmltext('<a href="data-sources/">%s</a>') % _('Data sources')
|
||||
if get_publisher().has_site_option('fields-blocks'):
|
||||
r += htmltext('<a href="blocks/">%s</a>') % _('Fields blocks')
|
||||
if get_publisher().get_backoffice_root().is_accessible('categories'):
|
||||
r += htmltext('<a href="categories/">%s</a>') % _('Categories')
|
||||
if is_global_accessible('forms'):
|
||||
r += htmltext('<a href="data-sources/">%s</a>') % _('Data sources')
|
||||
if get_publisher().has_site_option('fields-blocks'):
|
||||
r += htmltext('<a href="blocks/">%s</a>') % _('Fields blocks')
|
||||
if get_publisher().get_backoffice_root().is_accessible('categories'):
|
||||
r += htmltext('<a href="categories/">%s</a>') % _('Categories')
|
||||
r += htmltext('<a href="import" rel="popup">%s</a>') % _('Import')
|
||||
r += htmltext('<a class="new-item" href="new" rel="popup">%s</a>') % _('New Form')
|
||||
r += htmltext('</span>')
|
||||
|
@ -1777,7 +1822,7 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
def new(self):
|
||||
get_response().breadcrumb.append(('new', _('New')))
|
||||
if get_publisher().role_class.count() == 0:
|
||||
return template.error_page('forms', _('You first have to define roles.'))
|
||||
return template.error_page(self.section, _('You first have to define roles.'))
|
||||
formdefui = self.formdef_ui_class(None)
|
||||
form = formdefui.new_form_ui()
|
||||
if form.get_widget('cancel').parse():
|
||||
|
@ -1800,7 +1845,18 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
return r.getvalue()
|
||||
|
||||
def _q_lookup(self, component):
|
||||
return self.formdef_page_class(component)
|
||||
directory = self.formdef_page_class(component)
|
||||
global_access = is_global_accessible(self.section)
|
||||
if not global_access:
|
||||
user_roles = set(get_request().user.get_roles())
|
||||
management_roles = set()
|
||||
if directory.formdef.category:
|
||||
management_roles = {
|
||||
x.id for x in getattr(directory.formdef.category, 'management_roles') or []
|
||||
}
|
||||
if not management_roles.intersection(user_roles):
|
||||
raise AccessForbiddenError()
|
||||
return directory
|
||||
|
||||
def p_import(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
|
@ -1859,6 +1915,14 @@ class FormsDirectory(AccessControlled, Directory):
|
|||
except ValueError:
|
||||
error = True
|
||||
|
||||
global_access = is_global_accessible(self.section)
|
||||
if not global_access:
|
||||
management_roles = {x.id for x in getattr(formdef.category, 'management_roles', None) or []}
|
||||
user_roles = set(get_request().user.get_roles())
|
||||
if not user_roles.intersection(management_roles):
|
||||
error = True
|
||||
reason = _('unauthorized category')
|
||||
|
||||
if error:
|
||||
if reason:
|
||||
msg = _('Invalid File (%s)') % reason
|
||||
|
|
|
@ -40,6 +40,8 @@ class MailTemplatesDirectory(Directory):
|
|||
do_not_call_in_templates = True
|
||||
|
||||
def _q_traverse(self, path):
|
||||
if not get_publisher().get_backoffice_root().is_global_accessible('workflows'):
|
||||
raise errors.AccessForbiddenError()
|
||||
get_response().breadcrumb.append(('mail-templates/', _('Mail Templates')))
|
||||
return super()._q_traverse(path)
|
||||
|
||||
|
|
|
@ -66,6 +66,10 @@ from .logged_errors import LoggedErrorsDirectory
|
|||
from .mail_templates import MailTemplatesDirectory
|
||||
|
||||
|
||||
def is_global_accessible():
|
||||
return get_publisher().get_backoffice_root().is_global_accessible('workflows')
|
||||
|
||||
|
||||
def svg(tag):
|
||||
return '{http://www.w3.org/2000/svg}%s' % tag
|
||||
|
||||
|
@ -295,16 +299,30 @@ class WorkflowUI:
|
|||
def __init__(self, workflow):
|
||||
self.workflow = workflow
|
||||
|
||||
def get_categories(self):
|
||||
global_access = is_global_accessible()
|
||||
user_roles = set(get_request().user.get_roles())
|
||||
|
||||
def filter_function(category):
|
||||
if global_access:
|
||||
return True
|
||||
management_roles = {x.id for x in getattr(category, 'management_roles') or []}
|
||||
return bool(user_roles.intersection(management_roles))
|
||||
|
||||
return get_categories(WorkflowCategory, filter_function=filter_function)
|
||||
|
||||
def form_new(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.add(StringWidget, 'name', title=_('Workflow Name'), required=True, size=30)
|
||||
category_options = get_categories(WorkflowCategory)
|
||||
category_options = self.get_categories()
|
||||
if category_options:
|
||||
if is_global_accessible():
|
||||
category_options = [(None, '---', '')] + list(category_options)
|
||||
form.add(
|
||||
SingleSelectWidget,
|
||||
'category_id',
|
||||
title=_('Category'),
|
||||
options=[(None, '---', '')] + category_options,
|
||||
options=category_options,
|
||||
)
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
|
@ -332,8 +350,10 @@ class WorkflowUI:
|
|||
form.get_widget('name').set_error(_('This name is already used'))
|
||||
raise ValueError()
|
||||
|
||||
for f in ('name',):
|
||||
setattr(workflow, f, form.get_widget(f).parse())
|
||||
for f in ('name', 'category_id'):
|
||||
widget = form.get_widget(f)
|
||||
if widget:
|
||||
setattr(workflow, f, widget.parse())
|
||||
workflow.store()
|
||||
return workflow
|
||||
|
||||
|
@ -1487,7 +1507,9 @@ class WorkflowPage(Directory):
|
|||
return html_top('workflows', title)
|
||||
|
||||
def category(self):
|
||||
categories = sorted((misc.simplify(x.name), x.id, x.name, x.id) for x in WorkflowCategory.select())
|
||||
category_options = self.workflow_ui.get_categories()
|
||||
if is_global_accessible():
|
||||
category_options = [(None, '---', '')] + list(category_options)
|
||||
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.widgets.append(HtmlWidget('<p>%s</p>' % _('Select a category for this workflow.')))
|
||||
|
@ -1496,7 +1518,7 @@ class WorkflowPage(Directory):
|
|||
'category_id',
|
||||
title=_('Category'),
|
||||
value=self.workflow.category_id,
|
||||
options=[(None, '---', '')] + [x[1:] for x in categories],
|
||||
options=category_options,
|
||||
)
|
||||
|
||||
if not self.workflow.is_readonly():
|
||||
|
@ -1560,7 +1582,10 @@ class WorkflowPage(Directory):
|
|||
r += htmltext('<ul id="sidebar-actions">')
|
||||
if not self.workflow.is_readonly():
|
||||
r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
|
||||
r += htmltext('<li><a href="duplicate">%s</a></li>') % _('Duplicate')
|
||||
if not is_global_accessible() and self.workflow.id in ('_default', '_carddef_default'):
|
||||
r += htmltext('<li><a rel="popup" href="duplicate">%s</a></li>') % _('Duplicate')
|
||||
else:
|
||||
r += htmltext('<li><a href="duplicate">%s</a></li>') % _('Duplicate')
|
||||
r += htmltext('<li><a href="export">%s</a></li>') % _('Export')
|
||||
if get_publisher().snapshot_class:
|
||||
r += htmltext('<li><a rel="popup" href="history/save">%s</a></li>') % _('Save snapshot')
|
||||
|
@ -1746,7 +1771,7 @@ class WorkflowPage(Directory):
|
|||
|
||||
return redirect('.')
|
||||
|
||||
def edit(self, duplicate=False):
|
||||
def edit(self):
|
||||
form = self.workflow_ui.form_edit()
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
@ -1761,12 +1786,8 @@ class WorkflowPage(Directory):
|
|||
|
||||
self.html_top(title=_('Edit Workflow'))
|
||||
r = TemplateIO(html=True)
|
||||
if duplicate:
|
||||
get_response().breadcrumb.append(('edit', _('Duplicate')))
|
||||
r += htmltext('<h2>%s</h2>') % _('Duplicate Workflow')
|
||||
else:
|
||||
get_response().breadcrumb.append(('edit', _('Edit')))
|
||||
r += htmltext('<h2>%s</h2>') % _('Edit Workflow')
|
||||
get_response().breadcrumb.append(('edit', _('Edit')))
|
||||
r += htmltext('<h2>%s</h2>') % _('Edit Workflow')
|
||||
r += form.render()
|
||||
return r.getvalue()
|
||||
|
||||
|
@ -1801,6 +1822,31 @@ class WorkflowPage(Directory):
|
|||
return redirect('..')
|
||||
|
||||
def duplicate(self):
|
||||
if not is_global_accessible() and self.workflow.id in ('_default', '_carddef_default'):
|
||||
category_options = self.workflow_ui.get_categories()
|
||||
form = Form(enctype='multipart/form-data')
|
||||
form.widgets.append(HtmlWidget('<p>%s</p>' % _('Select a category for this workflow.')))
|
||||
form.add(
|
||||
SingleSelectWidget,
|
||||
'category_id',
|
||||
title=_('Category'),
|
||||
options=category_options,
|
||||
)
|
||||
form.add_submit('submit', _('Submit'))
|
||||
form.add_submit('cancel', _('Cancel'))
|
||||
if form.get_widget('cancel').parse():
|
||||
return redirect('.')
|
||||
|
||||
if not form.is_submitted() or form.has_errors():
|
||||
self.html_top(title=_('Duplicate Workflow'))
|
||||
r = TemplateIO(html=True)
|
||||
get_response().breadcrumb.append(('duplicate', _('Duplicate')))
|
||||
r += htmltext('<h2>%s</h2>') % _('Duplicate Workflow')
|
||||
r += form.render()
|
||||
return r.getvalue()
|
||||
|
||||
self.workflow_ui.workflow.category_id = form.get_widget('category_id').parse()
|
||||
|
||||
self.workflow_ui.workflow.id = None
|
||||
original_name = self.workflow_ui.workflow.name
|
||||
self.workflow_ui.workflow.name = '%s %s' % (self.workflow_ui.workflow.name, _('(copy)'))
|
||||
|
@ -1842,6 +1888,19 @@ class WorkflowsDirectory(Directory):
|
|||
get_response().breadcrumb.append(('workflows/', _('Workflows')))
|
||||
return super()._q_traverse(path)
|
||||
|
||||
def is_accessible(self, user):
|
||||
if is_global_accessible():
|
||||
return True
|
||||
|
||||
# check for access to specific categories
|
||||
user_roles = set(user.get_roles())
|
||||
for category in WorkflowCategory.select():
|
||||
management_roles = {x.id for x in getattr(category, 'management_roles') or []}
|
||||
if management_roles and user_roles.intersection(management_roles):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _q_index(self):
|
||||
self.html_top(title=_('Workflows'))
|
||||
r = TemplateIO(html=True)
|
||||
|
@ -1849,10 +1908,10 @@ class WorkflowsDirectory(Directory):
|
|||
r += htmltext('<div id="appbar">')
|
||||
r += htmltext('<h2>%s</h2>') % _('Workflows')
|
||||
r += htmltext('<span class="actions">')
|
||||
if get_publisher().has_site_option('mail-templates'):
|
||||
r += htmltext('<a href="mail-templates/">%s</a>') % _('Mail Templates')
|
||||
r += htmltext('<a href="data-sources/">%s</a>') % _('Data sources')
|
||||
if get_publisher().get_backoffice_root().is_accessible('categories'):
|
||||
if is_global_accessible():
|
||||
if get_publisher().has_site_option('mail-templates'):
|
||||
r += htmltext('<a href="mail-templates/">%s</a>') % _('Mail Templates')
|
||||
r += htmltext('<a href="data-sources/">%s</a>') % _('Data sources')
|
||||
r += htmltext('<a href="categories/">%s</a>') % _('Categories')
|
||||
r += htmltext('<a href="import" rel="popup">%s</a>') % _('Import')
|
||||
r += htmltext('<a class="new-item" rel="popup" href="new">%s</a>') % _('New Workflow')
|
||||
|
@ -1889,7 +1948,16 @@ class WorkflowsDirectory(Directory):
|
|||
else:
|
||||
unused_workflows.append(workflow)
|
||||
|
||||
categories = self.category_class.select()
|
||||
if is_global_accessible():
|
||||
categories = WorkflowCategory.select()
|
||||
else:
|
||||
categories = []
|
||||
user_roles = set(get_request().user.get_roles())
|
||||
for category in WorkflowCategory.select():
|
||||
management_roles = {x.id for x in getattr(category, 'management_roles') or []}
|
||||
if management_roles and user_roles.intersection(management_roles):
|
||||
categories.append(category)
|
||||
|
||||
self.category_class.sort_by_position(categories)
|
||||
|
||||
if categories:
|
||||
|
@ -1900,6 +1968,9 @@ class WorkflowsDirectory(Directory):
|
|||
workflow.category_id = default_category.id
|
||||
categories = [default_category] + categories
|
||||
|
||||
if is_global_accessible():
|
||||
categories = categories + [None]
|
||||
|
||||
def workflow_section(r, workflows):
|
||||
r += htmltext('<ul class="objects-list single-links">')
|
||||
for workflow in workflows:
|
||||
|
@ -1925,7 +1996,7 @@ class WorkflowsDirectory(Directory):
|
|||
r += htmltext('</li>')
|
||||
r += htmltext('</ul>')
|
||||
|
||||
for category in categories + [None]:
|
||||
for category in categories:
|
||||
if category is None:
|
||||
category_workflows = [x for x in workflows + unused_workflows if not x.category_id]
|
||||
else:
|
||||
|
@ -1970,7 +2041,18 @@ class WorkflowsDirectory(Directory):
|
|||
return r.getvalue()
|
||||
|
||||
def _q_lookup(self, component):
|
||||
return WorkflowPage(component)
|
||||
directory = WorkflowPage(component)
|
||||
global_access = is_global_accessible()
|
||||
if directory.workflow.id not in ('_default', '_carddef_default') and not global_access:
|
||||
user_roles = set(get_request().user.get_roles())
|
||||
management_roles = set()
|
||||
if directory.workflow.category:
|
||||
management_roles = {
|
||||
x.id for x in getattr(directory.workflow.category, 'management_roles') or []
|
||||
}
|
||||
if not management_roles.intersection(user_roles):
|
||||
raise errors.AccessForbiddenError()
|
||||
return directory
|
||||
|
||||
def p_import(self):
|
||||
form = Form(enctype='multipart/form-data')
|
||||
|
@ -2024,6 +2106,14 @@ class WorkflowsDirectory(Directory):
|
|||
except ValueError:
|
||||
error = True
|
||||
|
||||
global_access = is_global_accessible()
|
||||
if not global_access:
|
||||
management_roles = {x.id for x in getattr(workflow.category, 'management_roles', None) or []}
|
||||
user_roles = set(get_request().user.get_roles())
|
||||
if not user_roles.intersection(management_roles):
|
||||
error = True
|
||||
reason = _('unauthorized category')
|
||||
|
||||
if error:
|
||||
if reason:
|
||||
msg = _('Invalid File (%s)') % reason
|
||||
|
|
|
@ -15,12 +15,11 @@
|
|||
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from quixote import get_publisher, get_response, get_session, redirect
|
||||
from quixote.directory import Directory
|
||||
from quixote.html import TemplateIO, htmltext
|
||||
|
||||
from wcs.admin import utils
|
||||
from wcs.admin.categories import CardDefCategoriesDirectory
|
||||
from wcs.admin.forms import FormDefPage, FormDefUI, FormsDirectory, OptionsDirectory, html_top
|
||||
from wcs.admin.forms import FormDefPage, FormDefUI, FormsDirectory, OptionsDirectory
|
||||
from wcs.carddef import CardDef
|
||||
from wcs.categories import CardDefCategory
|
||||
from wcs.workflows import Workflow
|
||||
|
@ -33,11 +32,13 @@ from ..qommon.storage import NotEqual, Null
|
|||
class CardDefUI(FormDefUI):
|
||||
formdef_class = CardDef
|
||||
category_class = CardDefCategory
|
||||
section = 'cards'
|
||||
|
||||
|
||||
class CardDefOptionsDirectory(OptionsDirectory):
|
||||
category_class = CardDefCategory
|
||||
category_empty_choice = _('Select a category for this card model')
|
||||
section = 'cards'
|
||||
|
||||
|
||||
class CardDefPage(FormDefPage):
|
||||
|
@ -45,6 +46,7 @@ class CardDefPage(FormDefPage):
|
|||
formdef_export_prefix = 'card'
|
||||
formdef_ui_class = CardDefUI
|
||||
formdef_default_workflow = '_carddef_default'
|
||||
section = 'cards'
|
||||
|
||||
options_directory_class = CardDefOptionsDirectory
|
||||
|
||||
|
@ -60,9 +62,6 @@ class CardDefPage(FormDefPage):
|
|||
readonly_message = _('This card model is readonly.')
|
||||
management_view_label = _('List of cards')
|
||||
|
||||
def html_top(self, title):
|
||||
return html_top('cards', title)
|
||||
|
||||
def _q_index(self):
|
||||
self.html_top(title=self.formdef.name)
|
||||
r = TemplateIO(html=True)
|
||||
|
@ -234,6 +233,7 @@ class CardsDirectory(FormsDirectory):
|
|||
formdef_page_class = CardDefPage
|
||||
formdef_ui_class = CardDefUI
|
||||
|
||||
section = 'cards'
|
||||
top_title = _('Card Models')
|
||||
import_title = _('Import Card Model')
|
||||
import_submit_label = _('Import Card Model')
|
||||
|
@ -247,22 +247,17 @@ class CardsDirectory(FormsDirectory):
|
|||
'you should nevertheless check everything is ok. '
|
||||
)
|
||||
|
||||
def html_top(self, title):
|
||||
return html_top('cards', title)
|
||||
|
||||
def _q_traverse(self, path):
|
||||
get_response().breadcrumb.append(('cards/', _('Card Models')))
|
||||
return Directory._q_traverse(self, path)
|
||||
|
||||
def form_actions(self):
|
||||
r = TemplateIO(html=True)
|
||||
r += htmltext('<div id="appbar">')
|
||||
r += htmltext('<h2>%s</h2>') % _('Card Models')
|
||||
r += htmltext('<span class="actions">')
|
||||
r += htmltext('<a href="../forms/data-sources/">%s</a>') % _('Data sources')
|
||||
if get_publisher().has_site_option('fields-blocks'):
|
||||
r += htmltext('<a href="../forms/blocks/">%s</a>') % _('Fields blocks')
|
||||
r += htmltext('<a href="categories/">%s</a>') % _('Categories')
|
||||
if get_publisher().get_backoffice_root().is_global_accessible('forms'):
|
||||
r += htmltext('<a href="../forms/data-sources/">%s</a>') % _('Data sources')
|
||||
if get_publisher().has_site_option('fields-blocks'):
|
||||
r += htmltext('<a href="../forms/blocks/">%s</a>') % _('Fields blocks')
|
||||
if get_publisher().get_backoffice_root().is_global_accessible('cards'):
|
||||
r += htmltext('<a href="categories/">%s</a>') % _('Categories')
|
||||
r += htmltext('<a href="import" rel="popup">%s</a>') % _('Import')
|
||||
r += htmltext('<a class="new-item" href="new" rel="popup">%s</a>') % _('New Card Model')
|
||||
r += htmltext('</span>')
|
||||
|
|
|
@ -2577,6 +2577,25 @@ class FormBackOfficeStatusPage(FormStatusPage):
|
|||
|
||||
return response
|
||||
|
||||
def can_go_in_inspector(self):
|
||||
if get_publisher().get_backoffice_root().is_global_accessible('worflows'):
|
||||
return True
|
||||
if (
|
||||
get_publisher()
|
||||
.get_backoffice_root()
|
||||
.is_global_accessible(self.formdata.formdef.backoffice_section)
|
||||
):
|
||||
return True
|
||||
|
||||
user_roles = set(get_request().user.get_roles())
|
||||
for category in (self.formdata.formdef.category, self.formdata.formdef.workflow.category):
|
||||
if not category:
|
||||
continue
|
||||
management_roles = {x.id for x in getattr(category, 'management_roles') or []}
|
||||
if user_roles.intersection(management_roles):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_extra_context_bar(self, parent=None):
|
||||
formdata = self.filled
|
||||
|
||||
|
@ -2686,10 +2705,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
|
|||
'<div data-async-url="%suser-pending-forms"></div>' % formdata.get_url(backoffice=True)
|
||||
)
|
||||
|
||||
if not formdata.is_draft() and (
|
||||
get_publisher().get_backoffice_root().is_accessible('forms')
|
||||
or get_publisher().get_backoffice_root().is_accessible('workflows')
|
||||
):
|
||||
if not formdata.is_draft() and self.can_go_in_inspector():
|
||||
r += htmltext('<div class="extra-context">')
|
||||
r += htmltext('<p><a href="%sinspect">' % formdata.get_url(backoffice=True))
|
||||
r += htmltext('%s</a></p>') % _('Data Inspector')
|
||||
|
@ -3065,10 +3081,7 @@ class FormBackOfficeStatusPage(FormStatusPage):
|
|||
return r.getvalue()
|
||||
|
||||
def inspect(self):
|
||||
if not (
|
||||
get_publisher().get_backoffice_root().is_accessible('forms')
|
||||
or get_publisher().get_backoffice_root().is_accessible('workflows')
|
||||
):
|
||||
if not self.can_go_in_inspector():
|
||||
raise errors.AccessForbiddenError()
|
||||
charset = get_publisher().site_charset
|
||||
get_response().breadcrumb.append(('inspect', _('Data Inspector')))
|
||||
|
|
|
@ -98,21 +98,28 @@ class RootDirectory(BackofficeRootDirectory):
|
|||
return subdirectory in ('settings', 'users')
|
||||
return False
|
||||
|
||||
# if the directory defines a is_accessible method, use it.
|
||||
if hasattr(getattr(cls, subdirectory, None), 'is_accessible'):
|
||||
return getattr(cls, subdirectory).is_accessible(get_request().user)
|
||||
|
||||
return cls.is_global_accessible(subdirectory)
|
||||
|
||||
@classmethod
|
||||
def is_global_accessible(cls, subdirectory):
|
||||
if cls.check_admin_for_all():
|
||||
return True
|
||||
user_roles = set(get_request().user.get_roles())
|
||||
authorised_roles = set(get_cfg('admin-permissions', {}).get(subdirectory) or [])
|
||||
if authorised_roles:
|
||||
# access is governed by roles set in the settings panel
|
||||
return user_roles.intersection(authorised_roles)
|
||||
|
||||
# if the directory defines a is_accessible method, use it.
|
||||
if hasattr(getattr(cls, subdirectory, None), 'is_accessible'):
|
||||
return getattr(cls, subdirectory).is_accessible(get_request().user)
|
||||
|
||||
# as a last resort, for the other directories, the user needs to be
|
||||
# marked as admin
|
||||
return get_request().user.can_go_in_admin()
|
||||
|
||||
def check_admin_for_all(self):
|
||||
@classmethod
|
||||
def check_admin_for_all(cls):
|
||||
admin_for_all_file_path = os.path.join(get_publisher().app_dir, 'ADMIN_FOR_ALL')
|
||||
if not os.path.exists(os.path.join(admin_for_all_file_path)):
|
||||
return False
|
||||
|
|
|
@ -33,6 +33,7 @@ if not hasattr(types, 'ClassType'):
|
|||
|
||||
class CardDef(FormDef):
|
||||
_names = 'carddefs'
|
||||
backoffice_section = 'cards'
|
||||
data_sql_prefix = 'carddata'
|
||||
pickle_module_name = 'carddef'
|
||||
xml_root_node = 'carddef'
|
||||
|
|
|
@ -35,6 +35,7 @@ class Category(XmlStorableObject):
|
|||
|
||||
export_roles = None
|
||||
statistics_roles = None
|
||||
management_roles = None
|
||||
|
||||
# declarations for serialization
|
||||
XML_NODES = [
|
||||
|
@ -45,6 +46,7 @@ class Category(XmlStorableObject):
|
|||
('position', 'int'),
|
||||
('export_roles', 'roles'),
|
||||
('statistics_roles', 'roles'),
|
||||
('management_roles', 'roles'),
|
||||
]
|
||||
|
||||
def __init__(self, name=None):
|
||||
|
@ -149,6 +151,7 @@ class CardDefCategory(Category):
|
|||
('description', 'str'),
|
||||
('position', 'int'),
|
||||
('export_roles', 'roles'),
|
||||
('management_roles', 'roles'),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
@ -168,6 +171,7 @@ class WorkflowCategory(Category):
|
|||
('url_name', 'str'),
|
||||
('description', 'str'),
|
||||
('position', 'int'),
|
||||
('management_roles', 'roles'),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -85,6 +85,7 @@ class FormDef(StorableObject):
|
|||
data_sql_prefix = 'formdata'
|
||||
pickle_module_name = 'formdef'
|
||||
xml_root_node = 'formdef'
|
||||
backoffice_section = 'forms'
|
||||
verbose_name = _('Form')
|
||||
verbose_name_plural = _('Forms')
|
||||
|
||||
|
|
|
@ -30,11 +30,18 @@
|
|||
{% endwith %}
|
||||
</div>
|
||||
|
||||
{% if category.export_roles or category.statistics_roles %}
|
||||
{% if category.export_roles or category.statistics_roles or category.management_roles %}
|
||||
<div class="section">
|
||||
<h3>{% trans "Permissions" %}</h3>
|
||||
<div>
|
||||
<ul>
|
||||
{% if category.management_roles %}
|
||||
<li>{% trans "Management roles:" %}
|
||||
<ul>
|
||||
{% for role in category.management_roles %}<li>{{ role.name }}</li>{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if category.export_roles %}
|
||||
<li>{% trans "Export roles:" %}
|
||||
<ul>
|
||||
|
|
Loading…
Reference in New Issue