568 lines
21 KiB
Python
568 lines
21 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)'),},
|
|
{'id': 'extra.order_number',
|
|
'title': _(u'Order number'),}]
|
|
|
|
|
|
|
|
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/documents/">
|
|
<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>
|
|
<button data-status="assigning" data-popup="multi_attribute_task">Attribuer</button>
|
|
<button data-status="ready_to_send" data-action="send">Envoyer</button>
|
|
<button data-status="can_last_version_validate" data-popup="ask_multi_validation">Demander une validation</button>
|
|
<button data-status="has_last_version_accept" data-action="validate">Valider la dernière version</button>
|
|
<button data-status="has_last_version_refuse" data-action="refuse">Refuser la dernière version</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/documents/">
|
|
<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 OrderNumberColumn(column.LabelColumn):
|
|
grok.name('extra.order_number')
|
|
grok.adapts(Interface, Interface, ResultsTable)
|
|
header = _(u"Order number")
|
|
attribute = 'order_number'
|
|
weight = 130
|
|
|
|
|
|
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)
|