--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_thesaurus/zmi/thesaurus.py Tue Apr 14 17:52:05 2015 +0200
@@ -0,0 +1,479 @@
+#
+# Copyright (c) 2008-2015 Thierry Florac <tflorac AT ulthar.net>
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+from pyramid.response import Response
+from pyams_form.schema import CloseButton
+
+__docformat__ = 'restructuredtext'
+
+
+# import standard library
+import json
+
+from html import unescape
+
+# import interfaces
+from pyams_form.interfaces.form import IWidgetForm
+from pyams_skin.interfaces import IPageHeader, IInnerPage
+from pyams_skin.interfaces.container import ITableElementName, ITableElementEditor
+from pyams_skin.interfaces.viewlet import IToolbarAddingMenu
+from pyams_thesaurus.interfaces.loader import IThesaurusLoader, IThesaurusUpdaterConfiguration, \
+ IThesaurusExporterConfiguration, IThesaurusExporter
+from pyams_thesaurus.interfaces.thesaurus import IThesaurusInfo, IThesaurus, IThesaurusExtracts
+from pyams_thesaurus.zmi.interfaces import IThesaurusTermsMenu, IThesaurusView
+from pyams_utils.interfaces.tree import INode, ITree
+from pyams_zmi.interfaces import IAdminView
+from pyams_zmi.interfaces.menu import ISiteManagementMenu, IPropertiesMenu
+from pyams_zmi.layer import IAdminLayer
+from z3c.form.interfaces import IDataExtractedEvent, DISPLAY_MODE
+
+# import packages
+from pyams_form.form import AJAXAddForm, AJAXEditForm
+from pyams_pagelet.pagelet import pagelet_config
+from pyams_skin.layer import IPyAMSLayer
+from pyams_skin.page import InnerPage
+from pyams_skin.table import DefaultElementEditorAdapter
+from pyams_skin.viewlet.menu import MenuItem
+from pyams_skin.viewlet.toolbar import ToolbarMenuItem
+from pyams_template.template import template_config
+from pyams_thesaurus.loader.config import ThesaurusUpdaterConfiguration, ThesaurusExporterConfiguration
+from pyams_thesaurus.thesaurus import Thesaurus
+from pyams_thesaurus.zmi.extract import ThesaurusExtractsTable
+from pyams_utils.adapter import adapter_config, ContextRequestAdapter, ContextRequestViewAdapter
+from pyams_utils.registry import query_utility, get_utility
+from pyams_utils.traversing import get_parent
+from pyams_utils.url import absolute_url
+from pyams_viewlet.manager import viewletmanager_config
+from pyams_viewlet.viewlet import viewlet_config
+from pyams_zmi.control_panel import UtilitiesTable
+from pyams_zmi.form import AdminDialogAddForm, AdminEditForm
+from pyramid.events import subscriber
+from pyramid.exceptions import NotFound
+from pyramid.httpexceptions import HTTPBadRequest
+from pyramid.url import resource_url
+from pyramid.view import view_config
+from z3c.form import field, button
+from zope.component.interfaces import ISite
+from zope.interface import implementer, Interface, Invalid
+
+from pyams_thesaurus import _
+
+
+@adapter_config(context=(IThesaurus, IAdminLayer), provides=ITableElementName)
+class ThesaurusNameAdapter(ContextRequestAdapter):
+ """Thesaurus name adapter"""
+
+ @property
+ def name(self):
+ translate = self.request.localizer.translate
+ return translate(_("Thesaurus: {0}")).format(self.context.name)
+
+
+@viewlet_config(name='add-thesaurus.menu', context=ISite, layer=IAdminLayer,
+ view=UtilitiesTable, manager=IToolbarAddingMenu, permission='system.manage')
+class ThesaurusAddMenu(ToolbarMenuItem):
+ """Thesaurus add menu"""
+
+ label = _("Add thesaurus...")
+ label_css_class = 'fa fa-fw fa-language'
+ url = 'add-thesaurus.html'
+ modal_target = True
+
+
+@pagelet_config(name='add-thesaurus.html', context=ISite, layer=IPyAMSLayer, permission='system.manage')
+class ThesaurusAddForm(AdminDialogAddForm):
+ """Thesaurus add form"""
+
+ title = _("Utilities")
+ legend = _("Add thesaurus")
+ icon_css_class = 'fa fa-fw fa-language'
+
+ fields = field.Fields(IThesaurusInfo).select('name', 'title', 'subject', 'description', 'language', 'creator',
+ 'publisher', 'created')
+ ajax_handler = 'add-thesaurus.json'
+ edit_permission = None
+
+ def updateWidgets(self, prefix=None):
+ super(ThesaurusAddForm, self).updateWidgets(prefix)
+ self.widgets['description'].label_css_class = 'input textarea'
+
+ def create(self, data):
+ return Thesaurus()
+
+ def add(self, object):
+ manager = self.context.getSiteManager()
+ manager['thesaurus::{0}'.format(object.name.lower())] = object
+
+ def nextURL(self):
+ return absolute_url(self.context, self.request, 'utilities.html')
+
+
+@subscriber(IDataExtractedEvent, form_selector=ThesaurusAddForm)
+def handle_new_thesaurus_data_extraction(event):
+ """Handle new thesaurus data extraction"""
+ manager = event.form.context.getSiteManager()
+ name = event.data['name']
+ if 'thesaurus::{0}'.format(name.lower()) in manager:
+ event.form.widgets.errors += (Invalid(_("Specified thesaurus name is already used!")), )
+ thesaurus = query_utility(IThesaurus, name=name)
+ if thesaurus is not None:
+ event.form.widgets.errors += (Invalid(_("A thesaurus is already registered with this name!")), )
+
+
+@view_config(name='add-thesaurus.json', context=ISite, request_type=IPyAMSLayer,
+ permission='system.manage', renderer='json', xhr=True)
+class ThesaurusAJAXAddForm(AJAXAddForm, ThesaurusAddForm):
+ """Thesaurus add form, AJAX view"""
+
+
+@adapter_config(context=(IThesaurus, IAdminLayer, Interface), provides=ITableElementEditor)
+class ThesaurusTableElementEditor(DefaultElementEditorAdapter):
+ """Thesaurus table element editor"""
+
+ view_name = 'properties.html'
+ modal_target = False
+
+ @property
+ def url(self):
+ return resource_url(self.context, self.request, 'admin.html#{0}'.format(self.view_name))
+
+
+class ThesaurusHeaderAdapter(ContextRequestViewAdapter):
+ """Thesaurus views header adapter"""
+
+ @property
+ def back_url(self):
+ site = get_parent(self.context, ISite)
+ return absolute_url(site, self.request, 'admin.html#utilities.html')
+
+ back_target = None
+ icon_class = 'fa fa-fw fa-language'
+ title = _("Thesaurus management")
+
+
+@viewlet_config(name='properties.menu', layer=IAdminLayer, context=IThesaurus, manager=ISiteManagementMenu,
+ permission='system.view', weight=1)
+@viewletmanager_config(name='properties.menu', layer=IAdminLayer, context=IThesaurus, provides=IPropertiesMenu)
+@implementer(IPropertiesMenu)
+class ThesaurusPropertiesMenuItem(MenuItem):
+ """Thesaurus properties menu"""
+
+ label = _("Properties")
+ icon_class = 'fa fa-fw fa-language'
+ url = '#properties.html'
+
+
+@pagelet_config(name='properties.html', context=IThesaurus, layer=IPyAMSLayer, permission='system.view')
+@implementer(IWidgetForm, IInnerPage, IThesaurusView)
+class ThesaurusPropertiesEditForm(AdminEditForm):
+ """Thesaurus properties edit form"""
+
+ @property
+ def title(self):
+ translate = self.request.localizer.translate
+ return translate(_("Thesaurus: {0}")).format(self.context.name)
+
+ legend = _("Update thesaurus properties")
+ icon_css_class = 'fa fa-fw fa-language'
+
+ fields = field.Fields(IThesaurusInfo).select('name', 'title', 'subject', 'description', 'language', 'creator',
+ 'publisher', 'created')
+ ajax_handler = 'properties.json'
+ edit_permission = 'system.manage'
+
+ def updateWidgets(self, prefix=None):
+ super(ThesaurusPropertiesEditForm, self).updateWidgets(prefix)
+ self.widgets['name'].mode = DISPLAY_MODE
+ self.widgets['description'].label_css_class = 'input textarea'
+
+
+@view_config(name='properties.json', context=IThesaurus, request_type=IPyAMSLayer,
+ permission='system.manage', renderer='json', xhr=True)
+class ThesaurusPropertiesAJAXEditForm(AJAXEditForm, ThesaurusPropertiesEditForm):
+ """Thesaurus properties edit form, AJAX view"""
+
+
+@adapter_config(context=(IThesaurus, IAdminLayer, ThesaurusPropertiesEditForm), provides=IPageHeader)
+class ThesaurusPropertiesEditFormHeaderAdapter(ThesaurusHeaderAdapter):
+
+ subtitle = _("Thesaurus properties")
+
+
+#
+# Thesaurus terms views
+#
+
+@viewlet_config(name='terms.menu', context=IThesaurus, layer=IAdminLayer, manager=ISiteManagementMenu,
+ permission='system.view', weight=10)
+@viewletmanager_config(name='terms.menu', layer=IAdminLayer, context=IThesaurus, provides=IThesaurusTermsMenu)
+@implementer(IThesaurusTermsMenu)
+class ThesaurusTermsMenuItem(MenuItem):
+ """Thesaurus terms menu"""
+
+ label = _("Terms")
+ icon_class = 'fa fa-fw fa-tags'
+ url = '#terms.html'
+
+
+@pagelet_config(name='terms.html', context=IThesaurus, layer=IPyAMSLayer, permission='system.view')
+@template_config(template='templates/terms-tree.pt', layer=IPyAMSLayer)
+@implementer(IAdminView, IThesaurusView)
+class ThesaurusTermsView(InnerPage):
+ """Thesaurus terms view"""
+
+ def __init__(self, context, request):
+ super(ThesaurusTermsView, self).__init__(context, request)
+ self.extracts = IThesaurusExtracts(context)
+ self.extracts_view = ThesaurusExtractsTable(context, request)
+
+ def update(self):
+ super(ThesaurusTermsView, self).update()
+ self.extracts_view.update()
+
+ @property
+ def tree(self):
+ return sorted([INode(node) for node in ITree(self.context).get_root_nodes()],
+ key=lambda x: x.label)
+
+ @property
+ def search_query_params(self):
+ return json.dumps({'thesaurus_name': self.context.name})
+
+
+@adapter_config(context=(IThesaurus, IPyAMSLayer, ThesaurusTermsView), provides=IPageHeader)
+class ThesaurusTermsHeaderAdapter(ThesaurusHeaderAdapter):
+
+ subtitle = _("Thesaurus terms")
+
+
+class BaseTreeNodesView(object):
+ """Base tree nodes views"""
+
+ def __init__(self, request):
+ self.request = request
+
+ def get_nodes(self, term, result, subnodes=None):
+ extracts = IThesaurusExtracts(get_parent(term, IThesaurus))
+ translate = self.request.localizer.translate
+ for child in INode(term).get_children():
+ node = INode(child)
+ result.append({'label': node.label.replace("'", "'"),
+ 'view': absolute_url(node.context, self.request, 'properties.html'),
+ 'css_class': node.css_class,
+ 'extracts': [{'name': name,
+ 'title': extract.name,
+ 'color': extract.color,
+ 'used': name in (node.context.extracts or ())}
+ for name, extract in sorted(extracts.items(), key=lambda x: x[0])],
+ 'extensions': [{'title': translate(ext.label, context=self.request),
+ 'icon': ext.icon,
+ 'view': absolute_url(node.context, self.request, ext.target_view)}
+ for ext in node.context.query_extensions()],
+ 'expand': node.has_children()})
+ if subnodes and (node.context.label in subnodes):
+ nodes = result[-1]['subnodes'] = []
+ self.get_nodes(node.context, nodes, subnodes)
+
+
+@view_config(name='get-nodes.json', context=IThesaurus, request_type=IPyAMSLayer,
+ permission='view', renderer='json', xhr=True)
+class ThesaurusTermNodes(BaseTreeNodesView):
+ """Get thesaurus nodes"""
+
+ def __call__(self):
+ label = self.request.params.get('term')
+ if label:
+ label = unescape(label)
+ term = self.request.context.terms.get(label)
+ if term is None:
+ raise NotFound
+ result = []
+ self.get_nodes(term, result)
+ return {'term': label,
+ 'nodes': sorted(result, key=lambda x: x['label'])}
+
+
+@view_config(name='get-parent-nodes.json', context=IThesaurus, request_type=IPyAMSLayer,
+ permission='view', renderer='json', xhr=True)
+class ThesaurusTermParentNodes(BaseTreeNodesView):
+ """Get thesaurus parent nodes"""
+
+ def __call__(self):
+ label = self.request.params.get('term')
+ if label:
+ label = unescape(label)
+ term = self.request.context.terms.get(label)
+ if term is None:
+ raise NotFound
+ result = []
+ parents = list(reversed(term.get_parents()))
+ if parents:
+ self.get_nodes(parents[0], result, [t.label for t in parents])
+ return {'term': label,
+ 'nodes': result,
+ 'parent': parents[0].label}
+ else:
+ return {'term': label,
+ 'nodes': result,
+ 'parent': label}
+
+
+@view_config(name='switch-extract.json', context=IThesaurus, request_type=IPyAMSLayer,
+ permission='thesaurus.manage', renderer='json', xhr=True)
+def switch_term_extract(request):
+ """Term extract switcher"""
+ label = request.params.get('term')
+ extract_name = request.params.get('extract')
+ if not (label and extract_name):
+ raise HTTPBadRequest("Missing arguments")
+ thesaurus = request.context
+ term = thesaurus.terms.get(unescape(label))
+ if term is None:
+ raise HTTPBadRequest("Term not found")
+ extract = IThesaurusExtracts(thesaurus).get(extract_name)
+ if extract is None:
+ raise HTTPBadRequest("Extract not found")
+ if extract.name in (term.extracts or ()):
+ extract.remove_term(term)
+ else:
+ extract.add_term(term)
+ return {'term': term.label,
+ 'extract': extract.name,
+ 'color': extract.color,
+ 'used': extract.name in term.extracts}
+
+
+#
+# Terms import views
+#
+
+@viewlet_config(name='import.menu', context=IThesaurus, layer=IAdminLayer, manager=IThesaurusTermsMenu,
+ permission='system.manage', weight=10)
+class ThesaurusImportMenuItem(MenuItem):
+ """Thesaurus import menu"""
+
+ label = _("Import terms...")
+ icon_class = 'fa fa-fw fa-upload'
+ url = 'import.html'
+
+ modal_target = True
+
+
+class IThesaurusFormImportButtons(Interface):
+ """Thesaurus import form buttons"""
+
+ close = CloseButton(name='close', title=_("Close"))
+ add = button.Button(name='add', title=_("Import terms"))
+
+
+@pagelet_config(name='import.html', context=IThesaurus, layer=IPyAMSLayer, permission='system.manage')
+class ThesaurusImportForm(AdminDialogAddForm):
+ """Thesaurus import form"""
+
+ title = _("Thesaurus")
+ legend = _("Import thesaurus terms")
+ icon_css_class = 'fa fa-fw fa-upload'
+
+ fields = field.Fields(IThesaurusUpdaterConfiguration).select('clear', 'conflict_suffix', 'data', 'format',
+ 'import_synonyms', 'language', 'encoding')
+ buttons = button.Buttons(IThesaurusFormImportButtons)
+
+ ajax_handler = 'import.json'
+ edit_permission = None
+
+ def updateWidgets(self, prefix=None):
+ super(ThesaurusImportForm, self).updateWidgets(prefix)
+ self.widgets['language'].noValueMessage = _("-- automatic selection -- (if available)")
+ self.widgets['encoding'].noValueMessage = _("-- automatic selection -- (if available)")
+
+ def create(self, data):
+ configuration = ThesaurusUpdaterConfiguration(data)
+ loader = get_utility(IThesaurusLoader, name=configuration.format)
+ thesaurus = loader.load(data['data'], configuration)
+ target = self.context
+ if configuration.clear:
+ target.clear()
+ target.merge(configuration, thesaurus)
+
+ def update_content(self, content, data):
+ pass
+
+ def add(self, object):
+ pass
+
+ def nextURL(self):
+ return absolute_url(self.context, self.request, 'admin.html#terms.html')
+
+
+@view_config(name='import.json', context=IThesaurus, request_type=IPyAMSLayer,
+ permission='system.manage', renderer='json', xhr=True)
+class ThesaurusImportAJAXForm(AJAXAddForm, ThesaurusImportForm):
+ """Thesaurus import form, AJAX view"""
+
+
+#
+# Terms export views
+#
+
+@viewlet_config(name='export.menu', context=IThesaurus, layer=IAdminLayer, manager=IThesaurusTermsMenu,
+ permission='system.view', weight=15)
+class ThesaurusExportMenuItem(MenuItem):
+ """Thesaurus export menu"""
+
+ label = _("Export terms...")
+ icon_class = 'fa fa-fw fa-download'
+ url = 'export.html'
+
+ modal_target = True
+
+
+class IThesaurusFormExportButtons(Interface):
+ """Thesaurus export form buttons"""
+
+ close = CloseButton(name='close', title=_("Close"))
+ add = button.Button(name='add', title=_("Export terms"))
+
+
+@pagelet_config(name='export.html', context=IThesaurus, layer=IPyAMSLayer, permission='system.view')
+class ThesaurusExportForm(AdminDialogAddForm):
+ """Thesaurus export form"""
+
+ title = _("Thesaurus")
+ legend = _("export thesaurus terms")
+ icon_css_class = 'fa fa-fw fa-download'
+
+ fields = field.Fields(IThesaurusExporterConfiguration)
+ buttons = button.Buttons(IThesaurusFormExportButtons)
+
+ ajax_handler = 'export.xml'
+ download_target = 'download_frame'
+ edit_permission = None
+
+ configuration = None
+ exporter = None
+
+ def createAndAdd(self, data):
+ configuration = self.configuration = ThesaurusExporterConfiguration(data)
+ exporter = self.exporter = get_utility(IThesaurusExporter, name=configuration.format)
+ return exporter.export(self.context, configuration)
+
+
+@view_config(name='export.xml', context=IThesaurus, request_type=IPyAMSLayer, permission='system.manage')
+class ThesaurusExportAJAXForm(AJAXAddForm, ThesaurusExportForm):
+ """Thesaurus export form, AJAX view"""
+
+ def get_ajax_output(self, changes):
+ changes.seek(0)
+ headers = {'Content-Disposition': 'attachment; filename="{0}"'.format(self.configuration.filename)}
+ response = Response(content_type=self.exporter.handler.content_type,
+ headers=headers)
+ response.body_file = changes
+ return response