2010-04-21 10:50:16 +02:00
|
|
|
# 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
|
2012-01-26 17:32:10 +01:00
|
|
|
# along with this program; if not, see <http://www.gnu.org/licenses/>.
|
2010-04-21 10:50:16 +02:00
|
|
|
|
2023-06-21 17:00:49 +02:00
|
|
|
import xml.etree.ElementTree as ET
|
2022-01-09 20:28:02 +01:00
|
|
|
|
2020-11-02 17:40:49 +01:00
|
|
|
from quixote import get_publisher
|
2016-04-18 23:01:30 +02:00
|
|
|
from quixote.html import htmltext
|
2013-05-06 17:24:03 +02:00
|
|
|
|
2021-05-15 15:34:16 +02:00
|
|
|
from .qommon import _
|
2022-01-09 20:28:02 +01:00
|
|
|
from .qommon.misc import simplify, xml_node_text
|
2019-09-29 20:53:23 +02:00
|
|
|
from .qommon.storage import StorableObject
|
|
|
|
from .qommon.substitution import Substitutions
|
|
|
|
from .qommon.xml_storage import XmlStorableObject
|
2005-05-20 00:10:22 +02:00
|
|
|
|
2020-01-18 20:33:44 +01:00
|
|
|
|
2014-11-25 13:33:58 +01:00
|
|
|
class Category(XmlStorableObject):
|
2005-09-05 21:17:22 +02:00
|
|
|
_names = 'categories'
|
2020-08-10 13:59:51 +02:00
|
|
|
xml_root_node = 'category'
|
2022-01-09 15:30:27 +01:00
|
|
|
backoffice_class = 'wcs.admin.categories.CategoryPage'
|
2022-04-14 21:50:32 +02:00
|
|
|
backoffice_base_url = 'forms/categories/'
|
2024-01-19 15:13:57 +01:00
|
|
|
verbose_name_plural = _('Categories')
|
2022-01-09 15:30:27 +01:00
|
|
|
|
2005-09-05 21:17:22 +02:00
|
|
|
name = None
|
2006-03-04 23:21:46 +01:00
|
|
|
url_name = None
|
2006-10-10 12:02:57 +02:00
|
|
|
description = None
|
2007-03-12 09:54:03 +01:00
|
|
|
position = None
|
2015-09-20 19:54:33 +02:00
|
|
|
redirect_url = None
|
2005-05-20 00:10:22 +02:00
|
|
|
|
2021-12-18 13:07:09 +01:00
|
|
|
_export_roles = None
|
|
|
|
_statistics_roles = None
|
|
|
|
_management_roles = None
|
2021-05-14 14:55:38 +02:00
|
|
|
|
2014-11-25 12:13:40 +01:00
|
|
|
# declarations for serialization
|
2020-11-02 17:40:49 +01:00
|
|
|
XML_NODES = [
|
|
|
|
('name', 'str'),
|
|
|
|
('url_name', 'str'),
|
|
|
|
('description', 'str'),
|
|
|
|
('redirect_url', 'str'),
|
|
|
|
('position', 'int'),
|
2021-05-14 14:55:38 +02:00
|
|
|
('export_roles', 'roles'),
|
|
|
|
('statistics_roles', 'roles'),
|
2021-08-03 15:07:17 +02:00
|
|
|
('management_roles', 'roles'),
|
2020-11-02 17:40:49 +01:00
|
|
|
]
|
2014-11-25 12:13:40 +01:00
|
|
|
|
2020-11-02 17:40:49 +01:00
|
|
|
def __init__(self, name=None):
|
2005-09-05 21:17:22 +02:00
|
|
|
StorableObject.__init__(self)
|
|
|
|
self.name = name
|
2006-03-04 23:21:46 +01:00
|
|
|
|
2021-08-03 15:22:49 +02:00
|
|
|
@classmethod
|
|
|
|
def get_object_class(cls):
|
|
|
|
from .formdef import FormDef
|
|
|
|
|
|
|
|
return FormDef
|
|
|
|
|
2016-03-11 16:40:16 +01:00
|
|
|
@classmethod
|
2022-01-09 20:28:02 +01:00
|
|
|
def get_by_urlname(cls, url_name, ignore_errors=False):
|
2006-03-04 23:21:46 +01:00
|
|
|
objects = [x for x in cls.select() if x.url_name == url_name]
|
|
|
|
if objects:
|
|
|
|
return objects[0]
|
2022-01-09 20:28:02 +01:00
|
|
|
if ignore_errors:
|
|
|
|
return None
|
2006-03-04 23:21:46 +01:00
|
|
|
raise KeyError()
|
2006-08-16 13:23:18 +02:00
|
|
|
|
2022-01-09 20:28:02 +01:00
|
|
|
get_by_slug = get_by_urlname
|
|
|
|
|
|
|
|
@property
|
|
|
|
def slug(self):
|
|
|
|
return self.url_name
|
|
|
|
|
2016-03-11 16:40:16 +01:00
|
|
|
@classmethod
|
2006-08-16 13:23:18 +02:00
|
|
|
def has_urlname(cls, url_name):
|
|
|
|
objects = [x for x in cls.select() if x.url_name == url_name]
|
|
|
|
if objects:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
2022-04-14 21:50:32 +02:00
|
|
|
def get_admin_url(self):
|
|
|
|
return '%s/%s%s/' % (get_publisher().get_backoffice_url(), self.backoffice_base_url, self.id)
|
|
|
|
|
2024-02-09 18:55:17 +01:00
|
|
|
def store(
|
|
|
|
self, *args, comment=None, snapshot_store_user=True, application=None, store_snapshot=True, **kwargs
|
|
|
|
):
|
2019-12-18 09:58:58 +01:00
|
|
|
if not self.url_name:
|
|
|
|
existing_slugs = {
|
|
|
|
x.url_name: True for x in self.select(ignore_migration=True, ignore_errors=True)
|
|
|
|
}
|
|
|
|
base_slug = simplify(self.name)
|
2022-12-29 10:29:54 +01:00
|
|
|
if base_slug in get_publisher().root_directory_class._q_exports:
|
|
|
|
base_slug = 'cat-%s' % base_slug
|
2019-12-18 09:58:58 +01:00
|
|
|
self.url_name = base_slug
|
|
|
|
i = 2
|
|
|
|
while self.url_name in existing_slugs:
|
|
|
|
self.url_name = '%s-%s' % (base_slug, i)
|
|
|
|
i += 1
|
2022-01-09 15:30:27 +01:00
|
|
|
super().store(*args, **kwargs)
|
2024-02-09 18:55:17 +01:00
|
|
|
if get_publisher().snapshot_class and store_snapshot:
|
2022-03-16 12:16:39 +01:00
|
|
|
get_publisher().snapshot_class.snap(
|
2023-08-03 10:44:35 +02:00
|
|
|
instance=self, comment=comment, store_user=snapshot_store_user, application=application
|
2022-03-16 12:16:39 +01:00
|
|
|
)
|
2019-12-18 09:58:58 +01:00
|
|
|
|
2016-03-11 16:40:16 +01:00
|
|
|
@classmethod
|
2007-03-12 09:54:03 +01:00
|
|
|
def sort_by_position(cls, categories):
|
2019-11-12 14:52:42 +01:00
|
|
|
# move categories with no defined position to the end
|
|
|
|
categories.sort(key=lambda x: x.position if x and x.position is not None else 10000)
|
2007-03-12 09:54:03 +01:00
|
|
|
|
2011-12-15 18:20:07 +01:00
|
|
|
def remove_self(self):
|
2021-08-03 15:22:49 +02:00
|
|
|
for obj in self.get_object_class().select(lambda x: x.category_id == self.id):
|
|
|
|
obj.category_id = None
|
|
|
|
obj.store()
|
|
|
|
super().remove_self()
|
2011-12-15 18:20:07 +01:00
|
|
|
|
2014-03-03 18:26:46 +01:00
|
|
|
def get_substitution_variables(self, minimal=False):
|
2012-01-23 15:29:10 +01:00
|
|
|
d = {
|
|
|
|
'category_name': self.name,
|
|
|
|
'category_id': self.url_name,
|
2015-09-21 09:13:36 +02:00
|
|
|
'category_slug': self.url_name,
|
2012-01-23 15:29:10 +01:00
|
|
|
}
|
2014-03-03 18:26:46 +01:00
|
|
|
if not minimal:
|
|
|
|
d.update(
|
|
|
|
{
|
|
|
|
'category_description': self.description,
|
|
|
|
}
|
|
|
|
)
|
2012-01-23 15:29:10 +01:00
|
|
|
return d
|
|
|
|
|
2013-05-06 17:24:03 +02:00
|
|
|
def get_url(self):
|
|
|
|
base_url = get_publisher().get_frontoffice_url()
|
|
|
|
return '%s/%s/' % (base_url, self.url_name)
|
|
|
|
|
2017-07-16 20:14:02 +02:00
|
|
|
def get_description_html_text(self):
|
2014-04-22 17:38:55 +02:00
|
|
|
if not self.description:
|
|
|
|
return None
|
|
|
|
text = self.description
|
|
|
|
if text[0] != '<':
|
|
|
|
text = '<p>%s</p>' % text
|
|
|
|
return htmltext(text)
|
|
|
|
|
2021-05-14 15:11:43 +02:00
|
|
|
def has_permission(self, permission_name, user):
|
|
|
|
if user.is_admin:
|
|
|
|
return True
|
|
|
|
permission_roles = getattr(self, '%s_roles' % permission_name, None) or []
|
|
|
|
if not permission_roles:
|
|
|
|
return True
|
|
|
|
user_roles = set(user.get_roles()) if user else set()
|
|
|
|
return bool(user_roles.intersection([x.id for x in permission_roles]))
|
|
|
|
|
2022-01-09 20:28:02 +01:00
|
|
|
@classmethod
|
|
|
|
def object_category_xml_export(cls, obj, root, include_id):
|
|
|
|
if obj.category:
|
|
|
|
elem = ET.SubElement(root, 'category')
|
|
|
|
elem.attrib['slug'] = str(obj.category.slug)
|
|
|
|
elem.text = obj.category.name
|
|
|
|
if include_id:
|
|
|
|
elem.attrib['category_id'] = str(obj.category.id)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def object_category_xml_import(cls, obj, tree, include_id):
|
|
|
|
if tree.find('category') is None:
|
|
|
|
return
|
|
|
|
category_node = tree.find('category')
|
|
|
|
if include_id and category_node.attrib.get('category_id'):
|
|
|
|
category_id = str(category_node.attrib.get('category_id'))
|
|
|
|
if cls.has_key(category_id):
|
|
|
|
obj.category_id = category_id
|
|
|
|
elif category_node.attrib.get('slug'):
|
|
|
|
category = cls.get_by_slug(category_node.attrib.get('slug'), ignore_errors=True)
|
|
|
|
if category:
|
|
|
|
obj.category_id = category.id
|
|
|
|
else:
|
|
|
|
# legacy fallback to name lookup
|
|
|
|
category = xml_node_text(category_node)
|
|
|
|
for c in cls.select():
|
|
|
|
if c.name == category:
|
|
|
|
obj.category_id = c.id
|
|
|
|
break
|
|
|
|
|
2021-12-18 13:07:09 +01:00
|
|
|
@property
|
|
|
|
def export_roles(self):
|
|
|
|
return self._export_roles() if callable(self._export_roles) else self._export_roles
|
|
|
|
|
|
|
|
@export_roles.setter
|
|
|
|
def export_roles(self, value):
|
|
|
|
self._export_roles = value
|
|
|
|
|
|
|
|
@property
|
|
|
|
def statistics_roles(self):
|
|
|
|
return self._statistics_roles() if callable(self._statistics_roles) else self._statistics_roles
|
|
|
|
|
|
|
|
@statistics_roles.setter
|
|
|
|
def statistics_roles(self, value):
|
|
|
|
self._statistics_roles = value
|
|
|
|
|
|
|
|
@property
|
|
|
|
def management_roles(self):
|
|
|
|
return self._management_roles() if callable(self._management_roles) else self._management_roles
|
|
|
|
|
|
|
|
@management_roles.setter
|
|
|
|
def management_roles(self, value):
|
|
|
|
self._management_roles = value
|
|
|
|
|
2022-10-22 10:51:41 +02:00
|
|
|
def i18n_scan(self):
|
|
|
|
location = '%s:%s' % (self.xml_root_node, self.id)
|
|
|
|
yield location, None, self.name
|
|
|
|
yield location, None, self.description
|
|
|
|
|
2012-01-23 15:29:10 +01:00
|
|
|
|
2020-11-02 17:40:49 +01:00
|
|
|
class CardDefCategory(Category):
|
|
|
|
_names = 'carddef_categories'
|
|
|
|
xml_root_node = 'carddef_category'
|
2022-01-09 15:30:27 +01:00
|
|
|
backoffice_class = 'wcs.admin.categories.CardDefCategoryPage'
|
2022-04-14 21:50:32 +02:00
|
|
|
backoffice_base_url = 'cards/categories/'
|
2024-01-19 15:13:57 +01:00
|
|
|
verbose_name_plural = _('Categories')
|
2020-11-02 17:40:49 +01:00
|
|
|
|
|
|
|
# declarations for serialization
|
2021-05-14 14:55:38 +02:00
|
|
|
XML_NODES = [
|
|
|
|
('name', 'str'),
|
|
|
|
('url_name', 'str'),
|
|
|
|
('description', 'str'),
|
|
|
|
('position', 'int'),
|
|
|
|
('export_roles', 'roles'),
|
2021-08-03 15:07:17 +02:00
|
|
|
('management_roles', 'roles'),
|
2021-05-14 14:55:38 +02:00
|
|
|
]
|
2020-11-02 17:40:49 +01:00
|
|
|
|
2021-08-03 15:22:49 +02:00
|
|
|
@classmethod
|
|
|
|
def get_object_class(cls):
|
|
|
|
from .carddef import CardDef
|
|
|
|
|
|
|
|
return CardDef
|
|
|
|
|
2020-11-02 17:40:49 +01:00
|
|
|
|
2021-08-03 16:09:16 +02:00
|
|
|
class WorkflowCategory(Category):
|
|
|
|
_names = 'workflow_categories'
|
|
|
|
xml_root_node = 'workflow_category'
|
2022-01-09 15:30:27 +01:00
|
|
|
backoffice_class = 'wcs.admin.categories.WorkflowCategoryPage'
|
2022-04-14 21:50:32 +02:00
|
|
|
backoffice_base_url = 'workflows/categories/'
|
2021-08-03 16:09:16 +02:00
|
|
|
|
|
|
|
# declarations for serialization
|
|
|
|
XML_NODES = [
|
|
|
|
('name', 'str'),
|
|
|
|
('url_name', 'str'),
|
|
|
|
('description', 'str'),
|
2021-08-31 19:59:46 +02:00
|
|
|
('position', 'int'),
|
2021-08-03 15:07:17 +02:00
|
|
|
('management_roles', 'roles'),
|
2021-08-03 16:09:16 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_object_class(cls):
|
|
|
|
from .workflows import Workflow
|
|
|
|
|
|
|
|
return Workflow
|
|
|
|
|
|
|
|
|
2021-12-13 15:14:34 +01:00
|
|
|
class BlockCategory(Category):
|
|
|
|
_names = 'block_categories'
|
|
|
|
xml_root_node = 'block_category'
|
2022-01-09 15:30:27 +01:00
|
|
|
backoffice_class = 'wcs.admin.categories.BlockCategoryPage'
|
2022-04-14 21:50:32 +02:00
|
|
|
backoffice_base_url = 'forms/blocks/categories/'
|
2024-01-19 15:13:57 +01:00
|
|
|
verbose_name_plural = _('Categories')
|
2021-12-13 15:14:34 +01:00
|
|
|
|
|
|
|
# declarations for serialization
|
|
|
|
XML_NODES = [
|
|
|
|
('name', 'str'),
|
|
|
|
('url_name', 'str'),
|
|
|
|
('description', 'str'),
|
|
|
|
('position', 'int'),
|
|
|
|
]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_object_class(cls):
|
|
|
|
from .blocks import BlockDef
|
|
|
|
|
|
|
|
return BlockDef
|
|
|
|
|
|
|
|
|
2022-03-08 16:30:10 +01:00
|
|
|
class MailTemplateCategory(Category):
|
|
|
|
_names = 'mail_template_categories'
|
|
|
|
xml_root_node = 'mail_template_category'
|
|
|
|
backoffice_class = 'wcs.admin.categories.MailTemplateCategoryPage'
|
2022-04-14 21:50:32 +02:00
|
|
|
backoffice_base_url = 'workflows/mail-templates/categories/'
|
2022-03-08 16:30:10 +01:00
|
|
|
|
|
|
|
# declarations for serialization
|
|
|
|
XML_NODES = [
|
|
|
|
('name', 'str'),
|
|
|
|
('url_name', 'str'),
|
|
|
|
('description', 'str'),
|
|
|
|
('position', 'int'),
|
|
|
|
]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_object_class(cls):
|
|
|
|
from .mail_templates import MailTemplate
|
|
|
|
|
|
|
|
return MailTemplate
|
|
|
|
|
|
|
|
|
2022-12-06 16:24:10 +01:00
|
|
|
class CommentTemplateCategory(Category):
|
|
|
|
_names = 'comment_template_categories'
|
|
|
|
xml_root_node = 'comment_template_category'
|
|
|
|
backoffice_class = 'wcs.admin.categories.CommentTemplateCategoryPage'
|
|
|
|
backoffice_base_url = 'workflows/comment-templates/categories/'
|
2024-01-19 15:13:57 +01:00
|
|
|
verbose_name_plural = _('Categories')
|
2022-12-06 16:24:10 +01:00
|
|
|
|
|
|
|
# declarations for serialization
|
|
|
|
XML_NODES = [
|
|
|
|
('name', 'str'),
|
|
|
|
('url_name', 'str'),
|
|
|
|
('description', 'str'),
|
|
|
|
('position', 'int'),
|
|
|
|
]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_object_class(cls):
|
|
|
|
from .comment_templates import CommentTemplate
|
|
|
|
|
|
|
|
return CommentTemplate
|
|
|
|
|
|
|
|
|
2022-03-11 08:24:50 +01:00
|
|
|
class DataSourceCategory(Category):
|
|
|
|
_names = 'data_source_categories'
|
|
|
|
xml_root_node = 'data_source_category'
|
|
|
|
backoffice_class = 'wcs.admin.categories.DataSourceCategoryPage'
|
2022-04-14 21:50:32 +02:00
|
|
|
backoffice_base_url = 'forms/data-sources/categories/'
|
2024-01-19 15:13:57 +01:00
|
|
|
verbose_name_plural = _('Categories')
|
2022-03-11 08:24:50 +01:00
|
|
|
|
|
|
|
# declarations for serialization
|
|
|
|
XML_NODES = [
|
|
|
|
('name', 'str'),
|
|
|
|
('url_name', 'str'),
|
|
|
|
('description', 'str'),
|
|
|
|
('position', 'int'),
|
|
|
|
]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_object_class(cls):
|
|
|
|
from .data_sources import NamedDataSource
|
|
|
|
|
|
|
|
return NamedDataSource
|
|
|
|
|
|
|
|
|
2021-05-15 15:34:16 +02:00
|
|
|
Substitutions.register('category_name', category=_('General'), comment=_('Category Name'))
|
|
|
|
Substitutions.register('category_description', category=_('General'), comment=_('Category Description'))
|
|
|
|
Substitutions.register('category_id', category=_('General'), comment=_('Category Identifier'))
|