270 lines
10 KiB
Python
270 lines
10 KiB
Python
# w.c.s. - web application for online forms
|
|
# Copyright (C) 2005-2010 Entr'ouvert
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
|
|
from django.utils.translation import pgettext
|
|
from quixote import get_publisher, get_request, get_response, redirect
|
|
|
|
import wcs.admin.categories
|
|
import wcs.admin.forms
|
|
import wcs.admin.roles
|
|
import wcs.admin.settings
|
|
import wcs.admin.users
|
|
import wcs.admin.workflows
|
|
from wcs.formdef import FormDef
|
|
|
|
from ..qommon import _, errors, get_cfg, misc, template
|
|
from ..qommon.afterjobs import AfterJob
|
|
from ..qommon.backoffice import BackofficeRootDirectory
|
|
from ..qommon.backoffice.menu import html_top
|
|
from .cards import CardsDirectory
|
|
from .data_management import DataManagementDirectory
|
|
from .i18n import I18nDirectory
|
|
from .journal import JournalDirectory
|
|
from .management import ManagementDirectory
|
|
from .studio import StudioDirectory
|
|
from .submission import SubmissionDirectory
|
|
|
|
|
|
class RootDirectory(BackofficeRootDirectory):
|
|
_q_exports = ['', 'pending', 'statistics', ('menu.json', 'menu_json'), 'processing', 'journal']
|
|
|
|
forms = wcs.admin.forms.FormsDirectory()
|
|
roles = wcs.admin.roles.RolesDirectory()
|
|
settings = wcs.admin.settings.SettingsDirectory()
|
|
users = wcs.admin.users.UsersDirectory()
|
|
workflows = wcs.admin.workflows.WorkflowsDirectory()
|
|
management = ManagementDirectory()
|
|
journal = JournalDirectory()
|
|
studio = StudioDirectory()
|
|
cards = CardsDirectory()
|
|
data = DataManagementDirectory()
|
|
submission = SubmissionDirectory()
|
|
i18n = I18nDirectory()
|
|
|
|
menu_items = [
|
|
('submission/', _('Submission')),
|
|
('management/', _('Management')),
|
|
('data/', _('Cards')),
|
|
('studio/', _('Studio')),
|
|
('forms/', _('Forms Workshop'), {'sub': True}),
|
|
('cards/', _('Card Models'), {'sub': True}),
|
|
('workflows/', _('Workflows Workshop'), {'sub': True}),
|
|
('users/', _('Users'), {'check_display_function': roles.is_visible}),
|
|
('roles/', _('Roles'), {'check_display_function': roles.is_visible}),
|
|
('i18n/', _('Multilinguism'), {'check_display_function': lambda x: False}),
|
|
('settings/', _('Settings')),
|
|
]
|
|
|
|
def _q_traverse(self, path):
|
|
if not hasattr(self, self._q_translate(path[0]) or path[0]):
|
|
try:
|
|
# keep compatibility with previous versions, redirect from
|
|
# legacy URL to new ones under management/
|
|
FormDef.get_by_urlname(path[0], ignore_migration=True)
|
|
url = get_request().get_path_query()
|
|
url = url.replace('/backoffice/', '/backoffice/management/', 1)
|
|
return redirect(url)
|
|
except KeyError:
|
|
pass
|
|
get_response().add_javascript(['jquery.js', 'qommon.js', 'gadjo.js'])
|
|
if path and path[0] == 'categories':
|
|
# legacy /backoffice/categories/<...>, redirect.
|
|
return redirect('/backoffice/forms/' + '/'.join(path))
|
|
return super()._q_traverse(path)
|
|
|
|
@classmethod
|
|
def is_accessible(cls, subdirectory):
|
|
# check a backoffice directory is accessible to the current user
|
|
|
|
if getattr(get_response(), 'filter', {}) and get_response().filter.get('admin_for_all'):
|
|
# if admin for all is set, access is granted to everything
|
|
return True
|
|
|
|
if not get_request().user:
|
|
if not (get_publisher().user_class.exists()):
|
|
# setting up the site, access is granted to settings and users
|
|
# sections
|
|
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
|
|
if not get_request().user:
|
|
return False
|
|
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)
|
|
|
|
# as a last resort, for the other directories, the user needs to be
|
|
# marked as admin
|
|
return get_request().user.can_go_in_admin()
|
|
|
|
@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
|
|
with open(admin_for_all_file_path) as fd:
|
|
admin_for_all_contents = fd.read()
|
|
if not admin_for_all_contents:
|
|
# empty file, access is granted to everybody
|
|
return True
|
|
if get_request().get_environ('REMOTE_ADDR', '') in admin_for_all_contents.splitlines():
|
|
# if the file is not empty it should contain the list of authorized
|
|
# IP addresses.
|
|
return True
|
|
return False
|
|
|
|
def _q_access(self):
|
|
get_response().breadcrumb = [] # reinit, root the breadcrumb in the backoffice
|
|
get_response().breadcrumb.append(('backoffice/', _('Back Office')))
|
|
get_response().add_javascript(['jquery.js', 'qommon.admin.js'])
|
|
req = get_request()
|
|
|
|
if self.check_admin_for_all():
|
|
get_response().filter['admin_for_all'] = True
|
|
return
|
|
|
|
if get_publisher().user_class.exists():
|
|
user = req.user
|
|
if not user:
|
|
raise errors.AccessUnauthorizedError(
|
|
public_msg=_(
|
|
'Access to backoffice is restricted to authorized persons only. Please login.'
|
|
)
|
|
)
|
|
if not user.can_go_in_backoffice():
|
|
raise errors.AccessForbiddenError()
|
|
else:
|
|
# empty site
|
|
if get_cfg('idp'): # but already configured for IdP
|
|
raise errors.AccessUnauthorizedError()
|
|
|
|
get_response().filter['in_backoffice'] = True
|
|
|
|
def generate_header_menu(self, selected=None):
|
|
s = ['<ul id="sidepage-menu">\n']
|
|
for menu_item in self.get_menu_items():
|
|
if 'icon' not in menu_item:
|
|
continue
|
|
if menu_item.get('slug') == selected:
|
|
s.append('<li class="active">')
|
|
else:
|
|
s.append('<li>')
|
|
s.append('<a href="%(url)s" class="icon-%(icon)s">%(label)s</a></li>\n' % menu_item)
|
|
s.append('</ul>\n')
|
|
return ''.join(s)
|
|
|
|
def _q_index(self):
|
|
for directory in ('studio', 'management'):
|
|
if self.is_accessible(directory):
|
|
return redirect(directory + '/')
|
|
raise errors.AccessForbiddenError()
|
|
|
|
def menu_json(self):
|
|
return misc.json_response(self.get_menu_items())
|
|
|
|
def pending(self):
|
|
# kept as a redirection for compatibility with possible bookmarks
|
|
return redirect('.')
|
|
|
|
def statistics(self):
|
|
return redirect('management/statistics')
|
|
|
|
def _q_lookup(self, component):
|
|
if component in [str(x[0]).strip('/') for x in self.menu_items]:
|
|
if not self.is_accessible(component):
|
|
raise errors.AccessForbiddenError()
|
|
return getattr(self, component)
|
|
return super()._q_lookup(component)
|
|
|
|
def get_menu_items(self):
|
|
menu_items = []
|
|
backoffice_url = get_publisher().get_backoffice_url()
|
|
if not backoffice_url.endswith('/'):
|
|
backoffice_url += '/'
|
|
for item in self.menu_items:
|
|
if len(item) == 2:
|
|
item = list(item) + [{}]
|
|
k, v, options = item
|
|
slug = k.strip('/')
|
|
if not slug:
|
|
continue
|
|
display_function = options.get('check_display_function')
|
|
if display_function and not display_function(slug):
|
|
continue
|
|
if not self.is_accessible(slug):
|
|
continue
|
|
if callable(v):
|
|
label = v()
|
|
else:
|
|
label = _(v)
|
|
if slug == 'forms':
|
|
label = misc.site_encode(pgettext('studio', 'Forms'))
|
|
elif slug == 'cards':
|
|
label = misc.site_encode(pgettext('studio', 'Card Models'))
|
|
elif slug == 'workflows':
|
|
label = misc.site_encode(pgettext('studio', 'Workflows'))
|
|
menu_items.append(
|
|
{
|
|
'label': label,
|
|
'slug': slug,
|
|
'url': backoffice_url + k,
|
|
'sub': options.get('sub') or False,
|
|
}
|
|
)
|
|
|
|
if slug in (
|
|
'home',
|
|
'forms',
|
|
'workflows',
|
|
'users',
|
|
'roles',
|
|
'categories',
|
|
'settings',
|
|
'management',
|
|
'submission',
|
|
'studio',
|
|
'cards',
|
|
'data',
|
|
):
|
|
menu_items[-1]['icon'] = k.strip('/')
|
|
return menu_items
|
|
|
|
def processing(self):
|
|
html_top('/')
|
|
try:
|
|
job = AfterJob.get(get_request().form.get('job'))
|
|
except KeyError:
|
|
return redirect('.')
|
|
|
|
get_response().add_javascript(['jquery.js', 'afterjob.js'])
|
|
return template.QommonTemplateResponse(
|
|
templates=['wcs/backoffice/processing.html'], context={'job': job}
|
|
)
|