# -*- coding: utf8 -*- from collections import OrderedDict import json import datetime from Products.CMFPlone.utils import base_hasattr from plone import api from plone.dexterity.utils import createContentInContainer from five import grok from zope.component import getMultiAdapter, getUtility from zope import schema from zope.schema.interfaces import IVocabularyFactory from zope.interface import Interface, implementer, implements from z3c.form import form, field, button, widget from z3c.form.interfaces import IFieldWidget from zope.publisher.browser import BrowserView from z3c.form.widget import FieldWidget from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile from plone.formwidget.querystring.widget import QueryStringWidget from plone.i18n.normalizer.fr import normalizer from z3c.relationfield.schema import RelationChoice, RelationList from z3c.relationfield.interfaces import IHasRelations from z3c.relationfield import RelationValue from plone.formwidget.contenttree import ObjPathSourceBinder from plone.z3cform.layout import wrap_form import plone.app.querystring.querybuilder from plone.app.querystring.interfaces import IQuerystringRegistryReader from plone.registry.interfaces import IRegistry from plone.app.contentlisting.contentlisting import ContentListing from pfwbged.collection import _ grok.templatedir('templates') class PfwbQueryStringWidget(QueryStringWidget): input_template = ViewPageTemplateFile('input.pt') @implementer(IFieldWidget) def PfwbQueryStringFieldWidget(field, request): return FieldWidget(field, PfwbQueryStringWidget(request)) class ISearchForm(Interface): query = schema.List( title=u'Search terms', value_type=schema.Dict(value_type=schema.Field(), key_type=schema.TextLine()), required=False, ) sort_on = schema.TextLine(default=u'created') sort_reversed = schema.Bool(default=True) title = schema.TextLine(title=_(u'Title'), required=True) class SearchForm(form.Form): fields = field.Fields(ISearchForm) fields['query'].widgetFactory = PfwbQueryStringFieldWidget ignoreContext = True template = ViewPageTemplateFile('templates/view_form.pt') criterias_template = ViewPageTemplateFile('templates/criterias.pt') @button.buttonAndHandler(_(u'Save')) def handleApply(self, action): data, errors = self.extractData() if not data.get('title'): return current = plone.api.user.get_current() # When Anonymous current.id == 'acl_users' and current.getId() is None current_id = current.getId() if current_id is None: return members_folder = getattr(plone.api.portal.get(), 'Members') if base_hasattr(members_folder, current_id): location = getattr(members_folder, current_id) else: return item = createContentInContainer(location, 'pfwbgedcollection', title=data.get('title'), query=data.get('query'), extra_columns=[x for x in self.request.form.get('form-extra-columns', '').split(':') if x], sort_on=data.get('sort_on'), sort_reversed=data.get('sort_reversed')) self.request.response.redirect(item.absolute_url()) SearchFormView = wrap_form(SearchForm) class SearchView(grok.View): grok.context(Interface) grok.name('search') def render(self): url = api.portal.get().absolute_url() if not self.request.form.get('SearchableText'): self.request.response.redirect(url + '/pfwbsearch') return normalized = normalizer.normalize(unicode(self.request.form.get('SearchableText'), 'utf-8')).lower() self.request.response.redirect(url + '/pfwbsearch?sort_on=sortable_title&form.widgets.sort_on=sortable_title&form.widgets.query.i%3Arecords=SearchableText&form.widgets.query.o%3Arecords=plone.app.querystring.operation.string.contains&form.widgets.query.v%3Arecords=' + normalized) def form(self): f = SearchForm(self.context, self.request) f.update() return f.render() from collective.dms.basecontent.browser.listing import BaseTable, AuthorColumn, PMF from collective.dms.basecontent.browser import column from collective.dms.basecontent.browser.batch import PloneBatchProvider from pfwbged.policy.browser.tasksview import TasksTable, InformationsTable import plone.api class ResultsBatchProvider(PloneBatchProvider): grok.baseclass() defaultBatchLinkCSS = u'batch-link' batchSpacer = u'...' batchStartTag = u'

Pages : ' batchEndTag = u'

' def renderCurrentBatchLink(self, pagenumber, cssClass=u''): return self.renderBatchLink(pagenumber, cssClass) @property def b_start_str(self): return self.table.prefix +'-batchStart' @property def batchformkeys(self): return (self.table.prefix +'-batchSize', self.table.prefix +'-batchStart') class ResultsTable(BaseTable): def updateBatch(self): self.batchProvider = ResultsBatchProvider(self.context, self.request, self) self.batchProvider.update() def setUpColumns(self): columns = super(BaseTable, self).setUpColumns() extra_column_names = self.request.get('table-extra-columns', '').split(':') columns = [x for x in columns if not x.__name__.startswith('extra.') or x.__name__ in extra_column_names] self.folder_id = None try: if self.request.form.get('query', {})[0].get('i') == 'object_folders': if type(self.request.form.get('query', {})[0].get('v')) is list: self.folder_id = int(self.request.form.get('query', {})[0].get('v')[0]) else: self.folder_id = int(self.request.form.get('query', {})[0].get('v')) except (IndexError, ValueError, KeyError): pass if self.folder_id: columns = [x for x in columns if x.__name__ != 'dms.delete'] else: columns = [x for x in columns if x.__name__ != 'pfwbged.folder.unfile'] return columns def getExtraColumns(self): return self.request.get('table-extra-columns', '') def getBatchSize(self): return self.request.get('table-batchSize', '') def getPotentialExtraColumns(self): return [{'id': 'extra.internal_reference_number', 'title': _(u'Internal Number'),}, {'id': 'extra.sender', 'title': _(u'Sender'),}, {'id': 'extra.recipients', 'title': _(u'Recipient(s)'),}] class ResultsDocumentsTable(ResultsTable): def render(self): portal_url = api.portal.get().portal_url() return self.renderTable() + u"""
""" % {'portal_url': portal_url} class ResultsTasksTable(TasksTable): def updateBatch(self): self.batchProvider = ResultsBatchProvider(self.context, self.request, self) self.batchProvider.update() def setUpColumns(self): columns = super(TasksTable, self).setUpColumns() extra_column_names = self.request.get('table-extra-columns', '').split(':') columns = [x for x in columns if not x.__name__.startswith('extra.') or x.__name__ in extra_column_names] column_names = [column.__name__ for column in columns] return columns def getExtraColumns(self): return self.request.get('table-extra-columns', '') def getBatchSize(self): return self.request.get('table-batchSize', '') def getPotentialExtraColumns(self): return [] def render(self): portal_url = api.portal.get().portal_url() #abandon ask-for-refusal attribute take-responsibility return self.renderTable() + """
""" % {'portal_url': portal_url} class ResultsOpinionsTable(ResultsTasksTable): def render(self): # no multiactions for opinions return self.renderTable() class ContactsTasksTable(ResultsTable): def updateBatch(self): self.batchProvider = ResultsBatchProvider(self.context, self.request, self) self.batchProvider.update() def setUpColumns(self): columns = super(ResultsTable, self).setUpColumns() extra_column_names = self.request.get('table-extra-columns', '').split(':') columns = [x for x in columns if not x.__name__.startswith('extra.') or x.__name__ in extra_column_names] blacklist = ['dms.state', 'dms.author', 'pfwbged.folder.unfile'] columns = [x for x in columns if not x.__name__ in blacklist] return columns def getExtraColumns(self): return self.request.get('table-extra-columns', '') def getBatchSize(self): return self.request.get('table-batchSize', '') def getPotentialExtraColumns(self): return [] class ResultsFoldersTable(ResultsTable): def updateBatch(self): self.batchProvider = ResultsBatchProvider(self.context, self.request, self) self.batchProvider.update() def setUpColumns(self): columns = super(ResultsTable, self).setUpColumns() extra_column_names = self.request.get('table-extra-columns', '').split(':') columns = [x for x in columns if not x.__name__.startswith('extra.') or x.__name__ in extra_column_names] blacklist = ['dms.type', 'dms.state', 'pfwbged.folder.unfile'] columns = [x for x in columns if not x.__name__ in blacklist] return columns def getExtraColumns(self): return self.request.get('table-extra-columns', '') def getBatchSize(self): return self.request.get('table-batchSize', '') def getPotentialExtraColumns(self): return [] class ResultsInformationsTable(InformationsTable): def updateBatch(self): self.batchProvider = ResultsBatchProvider(self.context, self.request, self) self.batchProvider.update() def setUpColumns(self): columns = super(InformationsTable, self).setUpColumns() extra_column_names = self.request.get('table-extra-columns', '').split(':') columns = [x for x in columns if not x.__name__.startswith('extra.') or x.__name__ in extra_column_names] column_names = [column.__name__ for column in columns] return columns def getExtraColumns(self): return self.request.get('table-extra-columns', '') def getBatchSize(self): return self.request.get('table-batchSize', '') def getPotentialExtraColumns(self): return [] class AuthorColumn(AuthorColumn): grok.adapts(Interface, Interface, ResultsTable) class TypeColumn(column.Column): grok.name('dms.type') grok.adapts(Interface, Interface, ResultsTable) header = PMF(u"Type") weight = 10 def renderCell(self, item): factory = getUtility(IVocabularyFactory, 'plone.app.vocabularies.ReallyUserFriendlyTypes') vocabulary = factory(item) term = vocabulary.getTerm(item.portal_type) return term.title class InternalReferenceNumberColumn(column.LabelColumn): grok.name('extra.internal_reference_number') grok.adapts(Interface, Interface, ResultsTable) header = _(u"Internal Number") attribute = 'internal_reference_number' weight = 100 class SenderColumn(column.LabelColumn): grok.name('extra.sender') grok.adapts(Interface, Interface, ResultsTable) header = _(u"Sender") attribute = 'sender_as_text' weight = 110 class RecipientsColumn(column.LabelColumn): grok.name('extra.recipients') grok.adapts(Interface, Interface, ResultsTable) header = _(u"Recipient(s)") attribute = 'recipients_as_text' weight = 120 class CreationDateColumn(column.DateTimeColumn): grok.name('dms.creation') grok.adapts(Interface, Interface, ResultsTable) header = _(u'Creation Date') attribute = 'created' weight = 200 class TaskTypeColumn(column.Column): grok.adapts(Interface, Interface, ResultsTasksTable) header = PMF(u"Type") weight = 10 def renderCell(self, item): factory = getUtility(IVocabularyFactory, 'plone.app.vocabularies.ReallyUserFriendlyTypes') vocabulary = factory(item) term = vocabulary.getTerm(item.portal_type) return term.title class PositionColumn(column.Column): grok.name('contacts.position') grok.adapts(Interface, Interface, ContactsTasksTable) header = _(u"Position") weight = 20 def renderCell(self, item): try: return u', '.join(item.getObject().get_held_positions_titles()) except AttributeError: return '-' class UnfileColumn(column.IconColumn, column.LinkColumn): grok.name('pfwbged.folder.unfile') grok.adapts(Interface, Interface, ResultsTable) header = u'' iconName = "++resource++unlink_icon.png" linkContent = _('Unfile') linkCSS = 'edm-delete-popup' def getLinkURL(self, item): from pfwbged.folder.link import ILink for id, child in item.getObject().contentItems(): if not ILink.providedBy(child): continue if child.folder.to_id == self.table.folder_id: return item.getURL() + '/' + id + '/delete_confirmation' return '#' def get_appropriate_table_class(context, query): from plone.app.querystring.queryparser import parseFormquery parsed_query = parseFormquery(context, query) table_class = ResultsTable if parsed_query.get('portal_type'): portal_types = parsed_query.get('portal_type').get('query') if portal_types is None: return table_class if not (set(portal_types) - set(['opinion'])): # nothing but "opinions", use a more appropriate table return ResultsOpinionsTable if not (set(portal_types) - set(['task', 'opinion', 'validation', 'information'])): # nothing but "tasks", use a more appropriate table return ResultsTasksTable if not (set(portal_types) - set(['information'])): # nothing but "informations", use a more appropriate table return ResultsInformationsTable if not (set(portal_types) - set(['person', 'organization'])): # nothing but "contacts", use a more appropriate table return ContactsTasksTable if not (set(portal_types) - set(['pfwbgedfolder'])): # nothing but "folders", use a more appropriate table return ResultsFoldersTable if not set(portal_types).intersection(set(['task', 'opinion', 'validation', 'information', 'person', 'organization', 'pfwbgedfolder'])): # nothing but documents (hopefully) return ResultsDocumentsTable return table_class BATCH_SIZE = 10 class ContentListingView(BrowserView): """BrowserView for displaying query results""" def __call__(self, **kw): table_class = get_appropriate_table_class(plone.api.portal.get(), self.request.query) self.context.table = table_class(self.context, self.request) self.context.table.values = self.context self.context.table.batchSize = BATCH_SIZE self.context.table.update() return self.index(**kw) class QueryBuilder(plone.app.querystring.querybuilder.QueryBuilder): def __call__(self, query, *args, **kwargs): for i, param in enumerate(query): if param.get('o', '').startswith('plone.app.querystring.operation.date'): date = param.get('v') try: param = dict(param.copy()) if type(date) is list: # between... param['v'] = [datetime.datetime.strptime(x, '%d/%m/%Y').strftime('%Y-%m-%d') for x in date] else: param['v'] = datetime.datetime.strptime(date, '%d/%m/%Y').strftime('%Y-%m-%d') query[i] = param except ValueError: pass if param.get('o', '') == 'plone.app.querystring.operation.int.is': value = param.get('v') try: param = dict(param.copy()) if type(value) is list: param['v'] = int(value[0]) else: param['v'] = int(value) query[i] = param except ValueError: pass return plone.app.querystring.querybuilder.QueryBuilder.__call__(self, query, *args, **kwargs) def _makequery(self, query, *args, **kwargs): results = plone.app.querystring.querybuilder.QueryBuilder._makequery(self, query, *args, **kwargs) if isinstance(results, ContentListing) and kwargs.get('limit') != 1: # results may have been padded with None, but ContentListing # doesn't like that. # we do not touch the list in the casse of limit == 1 as it's used # to compute the number of results, and we want the correct number. results._basesequence = [x for x in results._basesequence if x is not None] return results def html_results(self, query): """html results, used for in the edit screen of a collection, used in the live update results""" # changes wrt original code is that we do not force a limit of 10 # elements options = dict(original_context=self.context) b_start = int(self.request.get('table-batchStart', 0)) b_size = int(self.request.get('table-batchSize', BATCH_SIZE)) results = self(query, sort_on=self.request.get('sort_on', None), sort_order=self.request.get('sort_order', None), b_start=b_start, b_size=b_size, limit=None, batch=True, brains=True ) self.request.response.setHeader('Cache-Control', 'no-cache') return getMultiAdapter((results, self.request), name='display_query_results')(**options) class RegistryConfiguration(BrowserView): def __call__(self): registry = getUtility(IRegistry) reader = getMultiAdapter( (registry, self.request), IQuerystringRegistryReader) data = reader() # getVocabularyValues in plone.app.string.registry could have # used an OrderedDict, but it's not so we sort portal types # again. data['indexes']['portal_type']['values'] = OrderedDict( sorted(data['indexes']['portal_type']['values'].items(), key=lambda t: t[1].get('title'))) # remove lessThanRelativeDate and largerThanRelativeDate operators for # date fields for field in data['indexes'].keys(): data_field = data['indexes'][field] for operation in ('plone.app.querystring.operation.date.lessThanRelativeDate', 'plone.app.querystring.operation.date.largerThanRelativeDate'): data_field['operations'] = [x for x in data_field['operations'] if x != operation] if operation in data_field['operators'].keys(): del data_field['operators'][operation] return json.dumps(data, indent=4)