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
Raw Normal View History

2018-07-11 21:38:56 +02:00
# -*- coding: utf8 -*-
2013-07-20 23:05:49 +02:00
from collections import OrderedDict
import json
import datetime
2013-07-20 23:05:49 +02:00
from Products.CMFPlone.utils import base_hasattr
2013-07-10 15:32:31 +02:00
from plone import api
from plone.dexterity.utils import createContentInContainer
from five import grok
2013-07-20 23:05:49 +02:00
from zope.component import getMultiAdapter, getUtility
from zope import schema
2013-10-09 18:18:23 +02:00
from zope.schema.interfaces import IVocabularyFactory
2013-07-17 15:04:31 +02:00
from zope.interface import Interface, implementer, implements
from z3c.form import form, field, button, widget
2013-07-10 15:32:31 +02:00
from z3c.form.interfaces import IFieldWidget
from zope.publisher.browser import BrowserView
2013-07-10 15:32:31 +02:00
from z3c.form.widget import FieldWidget
from zope.browserpage.viewpagetemplatefile import ViewPageTemplateFile
2013-07-10 15:32:31 +02:00
from plone.formwidget.querystring.widget import QueryStringWidget
from plone.i18n.normalizer.fr import normalizer
2013-07-10 15:32:31 +02:00
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
2013-07-20 23:05:49 +02:00
from plone.app.querystring.interfaces import IQuerystringRegistryReader
from plone.registry.interfaces import IRegistry
2014-04-01 17:47:14 +02:00
from plone.app.contentlisting.contentlisting import ContentListing
2013-07-10 15:32:31 +02:00
from pfwbged.collection import _
grok.templatedir('templates')
2013-07-10 15:32:31 +02:00
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()),
2013-07-10 15:32:31 +02:00
required=False,
)
sort_on = schema.TextLine(default=u'created')
sort_reversed = schema.Bool(default=True)
2013-07-20 15:31:17 +02:00
title = schema.TextLine(title=_(u'Title'), required=True)
2013-07-10 15:32:31 +02:00
class SearchForm(form.Form):
fields = field.Fields(ISearchForm)
2013-07-10 15:32:31 +02:00
fields['query'].widgetFactory = PfwbQueryStringFieldWidget
ignoreContext = True
template = ViewPageTemplateFile('templates/view_form.pt')
criterias_template = ViewPageTemplateFile('templates/criterias.pt')
2013-07-20 15:31:17 +02:00
@button.buttonAndHandler(_(u'Save'))
def handleApply(self, action):
2013-07-10 15:32:31 +02:00
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:
2013-07-10 15:32:31 +02:00
return
members_folder = getattr(plone.api.portal.get(), 'Members')
if base_hasattr(members_folder, current_id):
location = getattr(members_folder, current_id)
else:
return
2013-07-10 15:32:31 +02:00
item = createContentInContainer(location, 'pfwbgedcollection',
title=data.get('title'),
query=data.get('query'),
2013-11-15 14:44:22 +01:00
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'))
2013-07-10 15:32:31 +02:00
self.request.response.redirect(item.absolute_url())
SearchFormView = wrap_form(SearchForm)
class SearchView(grok.View):
grok.context(Interface)
grok.name('search')
2013-07-10 15:32:31 +02:00
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()
2013-10-09 18:18:23 +02:00
from collective.dms.basecontent.browser.listing import BaseTable, AuthorColumn, PMF
2013-07-15 10:39:56 +02:00
from collective.dms.basecontent.browser import column
from collective.dms.basecontent.browser.batch import PloneBatchProvider
2013-07-29 18:27:25 +02:00
from pfwbged.policy.browser.tasksview import TasksTable, InformationsTable
2013-07-17 15:04:31 +02:00
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')
2013-07-17 15:04:31 +02:00
2013-07-15 10:39:56 +02:00
class ResultsTable(BaseTable):
2013-07-17 15:04:31 +02:00
def updateBatch(self):
self.batchProvider = ResultsBatchProvider(self.context, self.request, self)
2013-07-17 15:04:31 +02:00
self.batchProvider.update()
2013-11-13 15:06:58 +01:00
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']
2013-11-13 15:06:58 +01:00
return columns
def getExtraColumns(self):
return self.request.get('table-extra-columns', '')
def getBatchSize(self):
return self.request.get('table-batchSize', '')
2014-02-06 10:15:56 +01:00
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)'),}]
2013-07-17 15:04:31 +02:00
2018-07-11 21:38:56 +02:00
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/">
2018-07-11 21:38:56 +02:00
<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>
2018-07-11 21:38:56 +02:00
</div>
""" % {'portal_url': portal_url}
2013-07-17 15:04:31 +02:00
class ResultsTasksTable(TasksTable):
def updateBatch(self):
self.batchProvider = ResultsBatchProvider(self.context, self.request, self)
2013-07-17 15:04:31 +02:00
self.batchProvider.update()
2013-07-15 10:39:56 +02:00
2013-11-13 15:06:58 +01:00
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', '')
2014-02-06 10:15:56 +01:00
def getPotentialExtraColumns(self):
return []
2018-07-11 21:38:56 +02:00
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/">
2018-07-11 21:38:56 +02:00
<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>
2018-07-11 21:38:56 +02:00
<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()
2013-11-13 15:06:58 +01:00
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', '')
2014-02-06 10:15:56 +01:00
def getPotentialExtraColumns(self):
return []
2013-07-15 10:39:56 +02:00
2014-03-05 10:45:35 +01:00
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']
2014-03-05 10:45:35 +01:00
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', '')
2014-03-05 10:45:35 +01:00
def getPotentialExtraColumns(self):
return []
2013-07-29 18:27:25 +02:00
class ResultsInformationsTable(InformationsTable):
def updateBatch(self):
self.batchProvider = ResultsBatchProvider(self.context, self.request, self)
self.batchProvider.update()
2013-11-13 15:06:58 +01:00
def setUpColumns(self):
2014-02-07 11:00:07 +01:00
columns = super(InformationsTable, self).setUpColumns()
2013-11-13 15:06:58 +01:00
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', '')
2014-02-07 11:00:07 +01:00
def getPotentialExtraColumns(self):
return []
2013-07-29 18:27:25 +02:00
2013-07-21 11:41:49 +02:00
class AuthorColumn(AuthorColumn):
grok.adapts(Interface, Interface, ResultsTable)
2013-10-09 18:18:23 +02:00
class TypeColumn(column.Column):
grok.name('dms.type')
2013-10-09 18:18:23 +02:00
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
2013-11-13 15:06:58 +01:00
class InternalReferenceNumberColumn(column.LabelColumn):
grok.name('extra.internal_reference_number')
grok.adapts(Interface, Interface, ResultsTable)
2014-02-06 10:16:21 +01:00
header = _(u"Internal Number")
2013-11-13 15:06:58 +01:00
attribute = 'internal_reference_number'
weight = 100
class SenderColumn(column.LabelColumn):
grok.name('extra.sender')
grok.adapts(Interface, Interface, ResultsTable)
2014-02-06 10:16:21 +01:00
header = _(u"Sender")
attribute = 'sender_as_text'
weight = 110
class RecipientsColumn(column.LabelColumn):
grok.name('extra.recipients')
grok.adapts(Interface, Interface, ResultsTable)
2014-02-06 10:16:21 +01:00
header = _(u"Recipient(s)")
attribute = 'recipients_as_text'
weight = 120
2014-03-14 10:37:22 +01:00
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)
2014-02-06 10:16:21 +01:00
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')
2014-02-09 15:34:56 +01:00
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')
2018-07-11 21:38:56 +02:00
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'])):
2013-07-29 18:27:25 +02:00
# nothing but "tasks", use a more appropriate table
2018-07-11 21:38:56 +02:00
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
2018-07-11 21:38:56 +02:00
return ContactsTasksTable
if not (set(portal_types) - set(['pfwbgedfolder'])):
2014-03-05 10:45:35 +01:00
# nothing but "folders", use a more appropriate table
2018-07-11 21:38:56 +02:00
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)
2013-07-15 10:39:56 +02:00
self.context.table.values = self.context
self.context.table.batchSize = BATCH_SIZE
2013-07-15 10:39:56 +02:00
self.context.table.update()
return self.index(**kw)
class QueryBuilder(plone.app.querystring.querybuilder.QueryBuilder):
2014-06-10 16:09:09 +02:00
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())
2014-07-03 10:56:09 +02:00
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')
2014-06-10 16:09:09 +02:00
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
2014-06-10 16:09:09 +02:00
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.
2014-06-10 16:09:09 +02:00
# 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
)
2013-07-20 13:57:18 +02:00
self.request.response.setHeader('Cache-Control', 'no-cache')
return getMultiAdapter((results, self.request),
name='display_query_results')(**options)
2013-07-20 23:05:49 +02:00
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]
2013-07-20 23:05:49 +02:00
return json.dumps(data, indent=4)