wcs/wcs/custom_views.py

266 lines
8.9 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 urllib.parse
import xml.etree.ElementTree as ET
from django.utils.encoding import force_text
from quixote import get_publisher
from wcs.backoffice.data_management import CardPage
from wcs.carddef import CardDef
from wcs.formdef import FormDef
from wcs.qommon.misc import simplify
from wcs.qommon.storage import Contains, Equal, NotContains, Nothing, StorableObject
from .qommon.misc import xml_node_text
class CustomView(StorableObject):
_names = 'custom-views'
title = None
slug = None
user_id = None
visibility = 'owner'
formdef_type = None
formdef_id = None
is_default = False
columns = None
filters = None
order_by = None
xml_root_node = 'custom_view'
@property
def user(self):
return get_publisher().user_class.get(self.user_id)
@user.setter
def user(self, value):
self.user_id = str(value.id)
@property
def formdef(self):
if self.formdef_type == 'formdef':
return FormDef.get(self.formdef_id)
else:
return CardDef.get(self.formdef_id)
@formdef.setter
def formdef(self, value):
self.formdef_id = str(value.id)
self.formdef_type = value.xml_root_node
def remove_self(self):
try:
formdef = self.formdef
except KeyError:
pass
else:
view_digest_key = 'custom-view:%s' % self.get_url_slug()
if view_digest_key in (formdef.digest_templates or {}):
del formdef.digest_templates[view_digest_key]
formdef.store()
super().remove_self()
def match(self, user, formdef):
if self.formdef_type != formdef.xml_root_node:
return False
if self.formdef_id != str(formdef.id):
return False
if self.visibility == 'owner' and (
user is None or not hasattr(user, 'id') or self.user_id != str(user.id)
):
return False
return True
def set_from_qs(self, qs):
parsed_qs = urllib.parse.parse_qsl(qs)
self.columns = {
'list': [
{'id': key} for (key, value) in parsed_qs if value == 'on' and not key.startswith('filter-')
],
}
columns_order = [x[1] for x in parsed_qs if x[0] == 'columns-order']
if columns_order:
field_order = columns_order[0].split(',')
def field_position(x):
if x['id'] in field_order:
return field_order.index(x['id'])
return 9999
self.columns['list'].sort(key=field_position)
order_by = [x[1] for x in parsed_qs if x[0] == 'order_by']
if order_by:
self.order_by = order_by[0]
self.filters = {key: value for (key, value) in parsed_qs if key.startswith('filter')}
def ensure_slug(self):
if self.slug:
return
clauses = [
Equal('formdef_type', self.formdef_type),
Equal('formdef_id', self.formdef_id),
Equal('visibility', self.visibility),
]
if self.visibility == 'owner':
clauses.append(Equal('user_id', self.user_id))
existing_slugs = {x.slug for x in self.select(clauses)}
base_slug = simplify(self.title)
if base_slug.startswith('user-'):
# prevent a slug starting with user- as it's used in URLs
base_slug = 'userx-' + base_slug[5:]
# prevent conflicts with system view names
from wcs.backoffice.management import FormPage
reserved_slugs = [
x if isinstance(x, str) else x[0] for x in FormPage._q_exports + CardPage._q_exports
] + ['ics']
if base_slug in reserved_slugs:
base_slug = 'x-' + base_slug
self.slug = base_slug
i = 2
while self.slug in existing_slugs:
self.slug = '%s-%s' % (base_slug, i)
i += 1
def get_url_slug(self):
if self.visibility == 'owner':
return 'user-%s' % self.slug
return self.slug
def store(self, *args, **kwargs):
self.ensure_slug()
return super().store(*args, **kwargs)
def get_columns(self):
if self.columns and 'list' in self.columns:
return [x['id'] for x in self.columns['list']]
else:
return []
def get_filter(self):
return self.filters.get('filter')
def get_status_filter_operator(self):
return self.filters.get('filter-operator', 'eq')
def get_filters_dict(self):
return self.filters
def get_default_filters(self):
return [key[7:] for key in self.filters if key.startswith('filter-')]
def get_criterias(self, formdef=None, compile_templates=False, keep_templates=False):
from wcs.backoffice.management import FormPage
if formdef is not None:
assert formdef.id == self.formdef_id
else:
formdef = self.formdef
form_page = FormPage(formdef=formdef, view=self, update_breadcrumbs=False)
criterias = form_page.get_view_criterias(
custom_view=self,
compile_templates=compile_templates,
keep_templates=keep_templates,
)
selected_filter = self.get_filter()
selected_status_filter_operator = self.get_status_filter_operator()
if selected_filter and selected_filter == 'all':
if selected_status_filter_operator == 'ne':
criterias.append(Nothing())
elif selected_filter:
if selected_filter == 'pending':
applied_filters = ['wf-%s' % x.id for x in formdef.workflow.get_not_endpoint_status()]
elif selected_filter == 'done':
applied_filters = ['wf-%s' % x.id for x in formdef.workflow.get_endpoint_status()]
else:
applied_filters = ['wf-%s' % selected_filter]
if applied_filters:
if selected_status_filter_operator == 'eq':
criterias.append(Contains('status', applied_filters))
elif selected_status_filter_operator == 'ne':
criterias.append(NotContains('status', applied_filters))
return criterias
def export_to_xml(self, charset=None):
root = ET.Element(self.xml_root_node)
fields = [
'title',
'slug',
'visibility',
'filters',
'columns',
'order_by',
]
for attribute in fields:
if getattr(self, attribute, None) is not None:
val = getattr(self, attribute)
el = ET.SubElement(root, attribute)
if attribute == 'columns':
for field_dict in self.columns.get('list') or []:
if not isinstance(field_dict, dict):
continue
for k, v in sorted(field_dict.items()):
text_value = force_text(v, charset, errors='replace')
ET.SubElement(el, k).text = text_value
elif isinstance(val, dict):
for k, v in sorted(val.items()):
text_value = force_text(v, charset, errors='replace')
ET.SubElement(el, k).text = text_value
elif isinstance(val, str):
el.text = force_text(val, charset, errors='replace')
else:
el.text = str(val)
return root
def init_with_xml(self, elem, charset):
fields = [
'title',
'slug',
'visibility',
'filters',
'columns',
'order_by',
]
for attribute in fields:
el = elem.find(attribute)
if el is None:
continue
if attribute == 'filters':
v = {}
for e in el:
v[e.tag] = xml_node_text(e)
setattr(self, attribute, v)
elif attribute == 'columns':
v = []
for e in el:
v.append({e.tag: xml_node_text(e)})
setattr(self, attribute, {'list': v})
else:
setattr(self, attribute, xml_node_text(el))