# 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 . 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))