This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
pfwbged.collection/src/pfwbged/collection/searchview.py

553 lines
20 KiB
Python

# -*- 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'<p class="listingBar">Pages : <span>'
batchEndTag = u'</span></p>'
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"""
<div class="multi-actions"
data-actions-url="%(portal_url)s/@@multiactions"
data-popup-action-base-url="%(portal_url)s/">
<button data-status="assigning" data-action="to_process">À traiter</button>
<button data-status="processing" data-action="to_considered">Pris en compte</button>
<button data-status="processing" data-action="to_noaction">Sans suite</button>
<button data-status="processing" data-popup="add_multi_information">Transmettre pour info</button>
</div>
""" % {'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() + """
<div class="multi-actions"
data-actions-url="%(portal_url)s/@@multiactions"
data-popup-action-base-url="%(portal_url)s/">
<button data-type="task" data-status="todo" data-action="take-responsibility">Prendre en charge</button>
<button data-status="todo" data-popup="multi_attribute_task">Attribuer</button>
<button data-type="task" data-status="in-progress" data-action="mark-as-done">Marquer comme fait </button>
<button data-type="information" data-status="todo" data-action="mark-as-done">Marquer comme lu</button>
</div>
""" % {'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)