wcs/wcs/carddef.py

266 lines
9.6 KiB
Python

# w.c.s. - web application for online forms
# Copyright (C) 2005-2019 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 sys
import types
from quixote import get_publisher
from wcs.carddata import CardData
from wcs.categories import CardDefCategory
from wcs.formdef import FormDef, get_formdefs_of_all_kinds
from .qommon import N_, _, misc
from .qommon.storage import Equal, ILike, NotEqual
from .qommon.template import Template
if not hasattr(types, 'ClassType'):
types.ClassType = type
class CardDef(FormDef):
_names = 'carddefs'
data_sql_prefix = 'carddata'
pickle_module_name = 'carddef'
xml_root_node = 'carddef'
verbose_name = N_('Card model')
verbose_name_plural = N_('Card models')
confirmation = False
category_class = CardDefCategory
def data_class(self, mode=None):
if 'carddef' not in sys.modules:
sys.modules['carddef'] = sys.modules[__name__]
if hasattr(sys.modules['carddef'], self.data_class_name):
data_class = getattr(sys.modules['carddef'], self.data_class_name)
# only use existing data class if it has a reference to this actual
# carddef
if data_class._formdef is self:
return data_class
if (get_publisher().is_using_postgresql() and not mode == 'files') or mode == 'sql':
from . import sql
table_name = sql.get_formdef_table_name(self)
cls = types.ClassType(
self.data_class_name, (sql.SqlCardData,), {'_formdef': self, '_table_name': table_name}
)
actions = sql.do_formdef_tables(self)
else:
cls = types.ClassType(
self.data_class_name,
(CardData,),
{'_names': 'card-%s' % self.internal_identifier, '_formdef': self},
)
actions = []
setattr(sys.modules['carddef'], self.data_class_name, cls)
setattr(sys.modules['wcs.carddef'], self.data_class_name, cls)
if actions:
for action in actions:
getattr(cls, action)()
return cls
@classmethod
def get_sql_new_id(cls, id_start):
from . import sql
return sql.get_carddef_new_id(id_start=id_start)
@classmethod
def sql_wipe(cls):
from . import sql
sql.carddef_wipe()
@classmethod
def get_default_workflow(cls):
from wcs.wf.remove import RemoveWorkflowStatusItem
from wcs.workflows import ChoiceWorkflowStatusItem, EditableWorkflowStatusItem, Workflow
workflow = Workflow(name=_('Default (cards)'))
workflow.id = '_carddef_default'
workflow.roles = {
'_viewer': _('Viewer'),
'_editor': _('Editor'),
}
status = workflow.add_status(_('Recorded'), 'recorded')
deleted_status = workflow.add_status(_('Deleted'), 'deleted')
editable = EditableWorkflowStatusItem()
editable.id = '_editable'
editable.by = ['_editor']
editable.label = _('Edit Card')
editable.status = status.id
editable.parent = status
status.items.append(editable)
action_delete = ChoiceWorkflowStatusItem()
action_delete.id = '_action_delete'
action_delete.by = ['_editor']
action_delete.label = _('Delete Card')
action_delete.status = deleted_status.id
action_delete.require_confirmation = True
action_delete.parent = status
status.items.append(action_delete)
remove = RemoveWorkflowStatusItem()
remove.id = '_remove'
remove.parent = deleted_status
deleted_status.items.append(remove)
return workflow
def get_url(self, backoffice=False):
# always return backoffice URL
base_url = get_publisher().get_backoffice_url() + '/data'
return '%s/%s/' % (base_url, self.url_name)
def get_backoffice_submission_url(self):
return self.get_url() + 'add/'
def get_admin_url(self):
base_url = get_publisher().get_backoffice_url()
return '%s/cards/%s/' % (base_url, self.id)
def get_api_url(self):
base_url = get_publisher().get_frontoffice_url()
return '%s/api/cards/%s/' % (base_url, self.url_name)
def store(self, comment=None):
self.roles = self.backoffice_submission_roles
return super().store(comment=comment)
@classmethod
def get_carddefs_as_data_source(cls):
carddefs_by_id = {}
for carddef in cls.select(lightweight=True, ignore_errors=True, order_by='name'):
if not carddef.digest_template:
continue
data_source_id = 'carddef:%s' % carddef.url_name
carddefs_by_id[carddef.id] = carddef
yield (carddef, carddef.name, data_source_id, None)
clauses = [Equal('formdef_type', 'carddef'), Equal('visibility', 'datasource')]
for custom_view in get_publisher().custom_view_class.select(clauses):
carddef = carddefs_by_id.get(custom_view.formdef_id)
if not carddef:
continue
data_source_id = 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)
yield (carddef, '%s - %s' % (carddef.name, custom_view.title), data_source_id, custom_view)
@classmethod
def get_data_source_custom_view(cls, data_source_id, carddef=None):
parts = data_source_id.split(':')
if len(parts) != 3:
return None
lookup_criterias = [
Equal('formdef_type', 'carddef'),
Equal('visibility', 'datasource'),
Equal('slug', parts[2]),
]
if carddef is not None:
lookup_criterias.append(Equal('formdef_id', carddef.id))
for custom_view in get_publisher().custom_view_class.select(lookup_criterias):
if custom_view.formdef.url_name == parts[1]:
return custom_view
return None
@classmethod
def get_data_source_items(
cls, data_source_id, query=None, limit=None, custom_view=None, get_by_id=None, get_by_text=None
):
from wcs.workflows import WorkflowStatusItem
assert data_source_id.startswith('carddef:')
parts = data_source_id.split(':')
try:
carddef = cls.get_by_urlname(parts[1])
except KeyError:
return []
criterias = [NotEqual('status', 'draft')]
order_by = None
if len(parts) > 2:
if custom_view is None:
custom_view = cls.get_data_source_custom_view(data_source_id, carddef=carddef)
if not custom_view:
return []
order_by = custom_view.order_by
criterias.extend(custom_view.get_criterias(formdef=carddef))
for criteria in criterias:
if not Template.is_template_string(criteria.value):
continue
criteria.value = WorkflowStatusItem.compute(criteria.value)
if query:
criterias.append(ILike('digest', query))
if get_by_id:
if int(get_by_id) >= 2 ** 31:
# out of range for postgresql integer type; would raise
# DataError.
return []
criterias.append(Equal('id', get_by_id))
if get_by_text:
criterias.append(Equal('digest', get_by_text))
items = [
x.get_data_source_structured_item()
for x in carddef.data_class().select(clause=criterias, order_by=order_by, limit=limit)
]
if order_by is None:
items.sort(key=lambda x: misc.simplify(x['text']))
return items
def usage_in_formdefs(self):
def is_used_in_formdef(formdef):
for field in formdef.fields or []:
data_source = getattr(field, 'data_source', None)
if not (data_source and data_source.get('type')):
continue
data_source_id = 'carddef:%s' % self.url_name
if data_source.get('type') == data_source_id:
return True
if data_source.get('type').startswith('%s:' % data_source_id):
# custom view
return True
return False
for formdef in get_formdefs_of_all_kinds():
if is_used_in_formdef(formdef):
yield formdef
@classmethod
def get_data_source_referenced_varnames(cls, data_source_id, formdef):
parts = data_source_id.split(':')
if len(parts) != 3:
return []
try:
carddef = cls.get_by_urlname(parts[1])
except KeyError:
return []
custom_view = cls.get_data_source_custom_view(data_source_id, carddef=carddef)
if custom_view is None:
return []
varnames = []
from .fields import Field
for criteria in custom_view.get_criterias(formdef=carddef):
if not isinstance(criteria.value, str):
continue
varnames.extend(Field.get_referenced_varnames(formdef, criteria.value))
return varnames