wcs/wcs/admin/blocks.py

294 lines
11 KiB
Python

# w.c.s. - web application for online forms
# Copyright (C) 2005-2020 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 xml.etree.ElementTree as ET
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.fields import FieldDefPage, FieldsDirectory
from wcs.backoffice.snapshots import SnapshotsDirectory
from wcs.blocks import BlockDef, BlockdefImportError
from wcs.qommon import N_, _, misc, template
from wcs.qommon.backoffice.menu import html_top
from wcs.qommon.form import FileWidget, Form, HtmlWidget, StringWidget
class BlockFieldDefPage(FieldDefPage):
blacklisted_attributes = ['condition']
def redirect_field_anchor(self, field):
anchor = '#itemId_%s' % field.id if field else ''
return redirect('../%s' % anchor)
class BlockDirectory(FieldsDirectory):
_q_exports = [
'',
'update_order',
'new',
'delete',
'export',
'settings',
('history', 'snapshots_dir'),
]
field_def_page_class = BlockFieldDefPage
blacklisted_types = ['page', 'table', 'table-select', 'tablerows', 'ranked-items', 'blocks']
support_import = False
readonly_message = N_('This block of fields is readonly.')
def __init__(self, section, *args, **kwargs):
self.section = section
super().__init__(*args, **kwargs)
self.snapshots_dir = SnapshotsDirectory(self.objectdef)
def _q_traverse(self, path):
get_response().breadcrumb.append(('%s/' % self.objectdef.id, self.objectdef.name))
return Directory._q_traverse(self, path)
def index_top(self):
r = TemplateIO(html=True)
r += htmltext('<div id="appbar">')
r += htmltext('<h2>%s</h2>') % self.objectdef.name
r += htmltext('</div>')
r += utils.last_modification_block(obj=self.objectdef)
r += get_session().display_message()
if not self.objectdef.fields:
r += htmltext('<div class="infonotice">%s</div>') % _('There are not yet any fields defined.')
return r.getvalue()
def index_bottom(self):
formdefs = list(self.objectdef.get_usage_formdefs())
formdefs.sort(key=lambda x: x.name.lower())
if not formdefs:
return
r = TemplateIO(html=True)
r += htmltext('<div class="section">')
r += htmltext('<h3>%s</h3>') % _('Usage')
r += htmltext('<ul class="objects-list single-links">')
for formdef in formdefs:
r += htmltext('<li><a href="%s">' % formdef.get_admin_url())
r += htmltext('%s</a></li>') % formdef.name
r += htmltext('</ul>')
r += htmltext('</div>')
return r.getvalue()
def get_new_field_form(self, page_id):
r = TemplateIO(html=True)
r += htmltext('<ul id="sidebar-actions">')
r += htmltext('<li><a href="delete" rel="popup">%s</a></li>') % _('Delete')
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')
r += htmltext('<li><a href="history/">%s</a></li>') % _('History')
r += htmltext('<li><a href="settings" rel="popup">%s</a></li>') % _('Settings')
r += htmltext('</ul>')
r += super().get_new_field_form(page_id=page_id)
return r.getvalue()
def delete(self):
form = Form(enctype='multipart/form-data')
if not self.objectdef.is_used():
form.widgets.append(
HtmlWidget('<p>%s</p>' % _('You are about to irrevocably delete this block.'))
)
form.add_submit('delete', _('Submit'))
else:
form.widgets.append(
HtmlWidget('<p>%s</p>' % _('This block is still used, it cannot be deleted.'))
)
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('..')
if not form.is_submitted() or form.has_errors():
get_response().breadcrumb.append(('delete', _('Delete')))
html_top(self.section, title=_('Delete Block'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s %s</h2>') % (_('Deleting Block:'), self.objectdef.name)
r += form.render()
return r.getvalue()
else:
self.objectdef.remove_self()
return redirect('..')
def export(self):
x = self.objectdef.export_to_xml(include_id=True)
misc.indent_xml(x)
response = get_response()
response.set_content_type('application/x-wcs-form')
response.set_header('content-disposition', 'attachment; filename=block-%s.wcs' % self.objectdef.slug)
return '<?xml version="1.0"?>\n' + ET.tostring(x).decode('utf-8')
def settings(self):
get_response().breadcrumb.append(('settings', _('Settings')))
form = Form()
form.add(StringWidget, 'name', title=_('Name'), value=self.objectdef.name, size=50)
disabled_slug = bool(self.objectdef.is_used())
widget = form.add(
StringWidget,
'slug',
title=_('Identifier'),
value=self.objectdef.slug,
size=50,
readonly=disabled_slug,
hint=_('It is for example used as prefix for variable names in the digest template below.'),
)
if disabled_slug:
widget.hint = _('The identifier can not be modified as the block is in use.')
form.add(
StringWidget,
'digest_template',
title=_('Digest Template'),
value=self.objectdef.digest_template,
size=50,
hint=_('Use %s_var_... to refer to fields.') % self.objectdef.slug.replace('-', '_'),
)
if not self.objectdef.is_readonly():
form.add_submit('submit', _('Submit'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('.')
if form.is_submitted() and not form.has_errors():
self.objectdef.name = form.get_widget('name').parse()
if form.get_widget('slug'):
self.objectdef.slug = form.get_widget('slug').parse()
widget_template = form.get_widget('digest_template')
if widget_template.parse() and 'form_var_' in widget_template.parse():
widget_template.set_error(
_('Wrong variable "form_var_…" detected. Please replace it by "%s_var_…".')
% self.objectdef.slug.replace('-', '_')
)
else:
self.objectdef.digest_template = widget_template.parse()
self.objectdef.store()
return redirect('.')
html_top(self.section, title=_('Settings'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Settings')
r += form.render()
return r.getvalue()
class BlocksDirectory(Directory):
_q_exports = ['', 'new', ('import', 'p_import')]
do_not_call_in_templates = True
def __init__(self, section):
super().__init__()
self.section = section
def _q_traverse(self, path):
get_response().breadcrumb.append(('blocks/', _('Fields Blocks')))
return super()._q_traverse(path)
def _q_lookup(self, component):
return BlockDirectory(self.section, BlockDef.get(component))
def _q_index(self):
html_top(self.section, title=_('Fields Blocks'))
return template.QommonTemplateResponse(
templates=['wcs/backoffice/blocks.html'],
context={'view': self, 'blocks': BlockDef.select(order_by='name')},
)
def new(self):
form = Form(enctype='multipart/form-data')
form.add(StringWidget, 'name', title=_('Name'), required=True, size=50)
form.add_submit('submit', _('Add'))
form.add_submit('cancel', _('Cancel'))
if form.get_widget('cancel').parse():
return redirect('.')
if form.is_submitted() and not form.has_errors():
block = BlockDef(name=form.get_widget('name').parse())
block.store()
return redirect('%s/' % block.id)
get_response().breadcrumb.append(('new', _('New Fields Block')))
html_top(self.section, title=_('New Fields Block'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('New Fields Block')
r += form.render()
return r.getvalue()
def p_import(self):
form = Form(enctype='multipart/form-data')
form.add(FileWidget, 'file', title=_('File'), required=True)
form.add_submit('submit', _('Import Fields Block'))
form.add_submit('cancel', _('Cancel'))
if form.get_submit() == 'cancel':
return redirect('.')
if form.is_submitted() and not form.has_errors():
try:
return self.import_submit(form)
except ValueError:
pass
get_response().breadcrumb.append(('import', _('Import')))
html_top(self.section, title=_('Import Fields Block'))
r = TemplateIO(html=True)
r += htmltext('<h2>%s</h2>') % _('Import Fields Block')
r += htmltext('<p>%s</p>') % _('You can install a new fields block by uploading a file.')
r += form.render()
return r.getvalue()
def import_submit(self, form):
fp = form.get_widget('file').parse().fp
error, reason = False, None
try:
blockdef = BlockDef.import_from_xml(fp)
except BlockdefImportError as e:
error = True
reason = _(e.msg)
if e.details:
reason += ' [%s]' % e.details
except ValueError:
error = True
if error:
if reason:
msg = _('Invalid File (%s)') % reason
else:
msg = _('Invalid File')
form.set_error('file', msg)
raise ValueError()
initial_blockdef_name = blockdef.name
blockdef_names = [x.name for x in BlockDef.select()]
copy_no = 1
while blockdef.name in blockdef_names:
if copy_no == 1:
blockdef.name = _('Copy of %s') % initial_blockdef_name
else:
blockdef.name = _('Copy of %(name)s (%(no)d)') % {
'name': initial_blockdef_name,
'no': copy_no,
}
copy_no += 1
blockdef.store()
get_session().message = ('info', _('This fields block has been successfully imported.'))
return redirect('%s/' % blockdef.id)