add document search
This commit is contained in:
parent
fbb648d366
commit
806e372068
|
@ -20,6 +20,14 @@
|
|||
permission="zope2.View"
|
||||
template="mail.pt"/>
|
||||
|
||||
<browser:page
|
||||
for="Products.CMFPlone.interfaces.IPloneSiteRoot"
|
||||
name="docsearch"
|
||||
class=".doc.SearchView"
|
||||
permission="zope2.View"
|
||||
template="doc.pt"/>
|
||||
|
||||
|
||||
<!-- Register an extension profile to make the product installable -->
|
||||
<genericsetup:registerProfile
|
||||
name="default"
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
|
||||
xmlns:tal="http://xml.zope.org/namespaces/tal"
|
||||
xmlns:metal="http://xml.zope.org/namespaces/metal"
|
||||
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
|
||||
lang="en"
|
||||
metal:use-macro="context/main_template/macros/master"
|
||||
i18n:domain="themis.search">
|
||||
<body>
|
||||
|
||||
<metal:main fill-slot="main">
|
||||
<tal:main-macro metal:define-macro="main">
|
||||
<tal:block tal:define="Batch python:modules['Products.CMFPlone'].Batch;
|
||||
mq python:modules['ZTUtils'].make_query;
|
||||
url batch_base_url | request/ACTUAL_URL;
|
||||
batchformkeys batchformkeys|nothing;
|
||||
b_size python:50; b_size request/b_size | b_size;
|
||||
b_start python:0;b_start request/b_start | b_start;
|
||||
results view/search_results;
|
||||
batch python:Batch(list(results), b_size, int(b_start), orphan=1);
|
||||
">
|
||||
|
||||
<h1 class="documentFirstHeading">Recherche de courrier</h1>
|
||||
|
||||
<form method="post" action="docsearch#results">
|
||||
<div tal:replace="structure view/form"/>
|
||||
</form>
|
||||
|
||||
<div id="results">
|
||||
<div class="resultsinfobox">
|
||||
<span>Nombre de résultats : <span tal:replace="python:len(results)"/></span>
|
||||
</div>
|
||||
|
||||
<div class="results-var">
|
||||
<div class="results">
|
||||
<table>
|
||||
<tal:entry tal:repeat="item batch">
|
||||
<tr tal:define="oddrow repeat/item/odd;" tal:attributes="class python: oddrow and 'odd' or 'even'">
|
||||
<td class="date"><span tal:condition="item/docDate" tal:replace="string: ${item/docDate/day}/${item/docDate/month}/${item/docDate/year}"></span></td>
|
||||
<td><a tal:attributes="href item/getURL" tal:content="item/Title"></a></td>
|
||||
</tr>
|
||||
</tal:entry>
|
||||
</table>
|
||||
</div>
|
||||
<style>
|
||||
.results table tr {
|
||||
line-height: 250%;
|
||||
}
|
||||
|
||||
.results table tr.even {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
td.date {
|
||||
text-align: right;
|
||||
width: 7em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div tal:condition="python: len(results)">
|
||||
<div metal:use-macro="view/batch_macros/macros/navigation" />
|
||||
</div>
|
||||
|
||||
<script>
|
||||
if(typeof(String.prototype.trim) === "undefined") {
|
||||
String.prototype.trim = function()
|
||||
{
|
||||
return String(this).replace(/^\s+|\s+$/g, '');
|
||||
};
|
||||
}
|
||||
|
||||
(function($) {
|
||||
$().ready(function() {
|
||||
$('input.contact-field').prevAll('select').change(function() {
|
||||
selected_option = $(this).val();
|
||||
$(this).nextAll('input.contact-field').val(selected_option.split('(')[0].trim());
|
||||
});
|
||||
});
|
||||
})(jQuery);
|
||||
</script>
|
||||
|
||||
</tal:block>
|
||||
</tal:main-macro>
|
||||
</metal:main>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,5 +1,182 @@
|
|||
from five import grok
|
||||
from zope import interface, schema, component
|
||||
from zope.interface import implements
|
||||
from Products.Five import BrowserView
|
||||
from Products.CMFCore.utils import getToolByName
|
||||
from zope.schema.interfaces import IContextSourceBinder
|
||||
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
|
||||
from z3c.form import form, field, button, widget
|
||||
from z3c.form.ptcompat import ViewPageTemplateFile
|
||||
from z3c.form.browser.radio import RadioWidget
|
||||
from z3c.form.browser.checkbox import CheckBoxWidget
|
||||
from z3c.form.browser.text import TextWidget
|
||||
from z3c.form.interfaces import ITextWidget
|
||||
import z3c.form.interfaces
|
||||
|
||||
from themis.fields.vocabs import get_terms_for_persons
|
||||
import tabellio.config.utils
|
||||
from themis.fields import Commission
|
||||
|
||||
from interfaces import MessageFactory as _
|
||||
|
||||
def FieldRadioboxesWidget(field, request):
|
||||
return widget.FieldWidget(field, RadioWidget(request))
|
||||
|
||||
def FieldCheckboxWidget(field, request):
|
||||
return widget.FieldWidget(field, CheckBoxWidget(request))
|
||||
|
||||
@grok.provider(IContextSourceBinder)
|
||||
def possible_categories(context):
|
||||
catalog = getToolByName(context, 'portal_catalog')
|
||||
possible_categories = catalog.uniqueValuesFor('docCategory')
|
||||
terms = []
|
||||
for category in possible_categories:
|
||||
if not category:
|
||||
continue
|
||||
category_id = category.encode('ascii', 'replace')
|
||||
terms.append(SimpleVocabulary.createTerm(category_id, category_id, category))
|
||||
terms.sort(cmp_term)
|
||||
return SimpleVocabulary(terms)
|
||||
|
||||
|
||||
def cmp_term(x, y):
|
||||
from plone.i18n.normalizer.fr import normalizer
|
||||
return cmp(normalizer.normalize(x.title), normalizer.normalize(y.title))
|
||||
|
||||
@grok.provider(IContextSourceBinder)
|
||||
def possible_topics(context):
|
||||
catalog = getToolByName(context, 'portal_catalog')
|
||||
topics = tabellio.config.utils.get_topics_dict()
|
||||
terms = []
|
||||
for key, value in sorted(topics.items()):
|
||||
topic_id = key
|
||||
topic_str = value[0]
|
||||
terms.append(SimpleVocabulary.createTerm(topic_id, topic_id, topic_str))
|
||||
return SimpleVocabulary(terms)
|
||||
|
||||
|
||||
@grok.provider(IContextSourceBinder)
|
||||
def possible_sessions(context):
|
||||
terms = []
|
||||
for term in tabellio.config.utils.get_legisl_and_sessions():
|
||||
term_id = term.encode('ascii', 'replace')
|
||||
terms.append(SimpleVocabulary.createTerm(term_id, term_id, term))
|
||||
return SimpleVocabulary(terms)
|
||||
|
||||
@grok.provider(IContextSourceBinder)
|
||||
def possible_status(context):
|
||||
return SimpleVocabulary([
|
||||
SimpleVocabulary.createTerm('all', 'all', _(u'All')),
|
||||
SimpleVocabulary.createTerm('private', 'private', _(u'Private')),
|
||||
SimpleVocabulary.createTerm('pending', 'pending', _(u'Pending')),
|
||||
SimpleVocabulary.createTerm('published', 'published', _(u'Public'))])
|
||||
|
||||
@grok.provider(IContextSourceBinder)
|
||||
def possible_commission_status(context):
|
||||
portal = getToolByName(context, 'portal_url').getPortalObject()
|
||||
fti = getattr(portal.portal_types, 'ProjetD')
|
||||
return fti.lookupSchema().get('etat_en_commission').vocabulary
|
||||
|
||||
|
||||
class IDocSearchForm(interface.Interface):
|
||||
text = schema.TextLine(title=_(u'Text'), required=False)
|
||||
number = schema.TextLine(title=_(u'Document Number'), required=False)
|
||||
category = schema.Choice(title=_(u'Category'), required=False,
|
||||
source=possible_categories)
|
||||
topics = schema.List(title=_(u'Topics'), required=False,
|
||||
value_type=schema.Choice(required=False,
|
||||
source=possible_topics));
|
||||
person = schema.TextLine(title=_(u'Person'), required=False)
|
||||
session = schema.Choice(title=_(u'Legislature / Session'), required=False,
|
||||
source=possible_sessions)
|
||||
meeting_date_min = schema.Date(title=_(u'Meeting Date (minimum)'), required=False)
|
||||
meeting_date_max = schema.Date(title=_(u'Meeting Date (maximum)'), required=False)
|
||||
status = schema.Choice(title=_(u'Status'), required=True, default='all',
|
||||
source=possible_status)
|
||||
commission = Commission(title=_(u'Commission'), required=False)
|
||||
commission_status = schema.Choice(title=_(u'Commission Status'),
|
||||
required=False, source=possible_commission_status)
|
||||
|
||||
class DocSearchForm(form.Form):
|
||||
fields = field.Fields(IDocSearchForm)
|
||||
fields['topics'].widgetFactory = FieldCheckboxWidget
|
||||
#fields['person'].widgetFactory = FieldPersonWidget
|
||||
ignoreContext = True
|
||||
template = ViewPageTemplateFile('view_form.pt')
|
||||
|
||||
@button.buttonAndHandler(_(u'Submit'))
|
||||
def handleApply(self, action):
|
||||
pass
|
||||
|
||||
|
||||
class SearchView(BrowserView):
|
||||
pass
|
||||
batch_macros = ViewPageTemplateFile('batch_macros.pt')
|
||||
|
||||
def form(self):
|
||||
f = DocSearchForm(self.context, self.request)
|
||||
f.update()
|
||||
return f.render()
|
||||
|
||||
def search_results(self):
|
||||
f = DocSearchForm(self.context, self.request)
|
||||
f.update()
|
||||
|
||||
if not self.request.form.get('form.buttons.submit'):
|
||||
return []
|
||||
data, errors = f.extractData()
|
||||
kw = {}
|
||||
|
||||
if data.get('meeting_date_min') and data.get('meeting_date_max'):
|
||||
kw['docMeetingDate'] = {'query': [data.get('meeting_date_min'),
|
||||
data.get('meeting_date_max')],
|
||||
'range': 'minmax'}
|
||||
elif data.get('meeting_date_min'):
|
||||
kw['docMeetingDate'] = {'query': data.get('meeting_date_min'), 'range': 'min'}
|
||||
elif data.get('meeting_date_max'):
|
||||
kw['docMeetingDate'] = {'query': data.get('meeting_date_max'), 'range': 'max'}
|
||||
|
||||
if data.get('text'):
|
||||
kw['docSearchableText'] = data.get('text')
|
||||
if data.get('number'):
|
||||
kw['docNumber'] = data.get('number')
|
||||
if data.get('category'):
|
||||
kw['docCategory'] = possible_categories(self.context).by_token.get(data.get('category')).title
|
||||
else:
|
||||
portal = getToolByName(self.context, 'portal_url').getPortalObject()
|
||||
kw['portal_type'] = []
|
||||
for typeid in portal.portal_types.objectIds():
|
||||
if '(D)' in getattr(portal.portal_types, typeid).Title():
|
||||
kw['portal_type'].append(typeid)
|
||||
if data.get('person'):
|
||||
kw['docPersonsFuzzy'] = data.get('person')
|
||||
|
||||
if data.get('session'):
|
||||
kw['session'] = tabellio.config.utils.get_list_of_sessions(data.get('session'))
|
||||
|
||||
if data.get('topics'):
|
||||
kw['docTopics'] = data.get('topics')
|
||||
|
||||
if data.get('commission'):
|
||||
kw['docCommissions'] = data.get('commission')
|
||||
|
||||
if data.get('commission_status'):
|
||||
kw['docCommissionState'] = data.get('commission_status')
|
||||
|
||||
if data.get('status') and data.get('status') != 'all':
|
||||
kw['review_state'] = data.get('status')
|
||||
|
||||
kw['sort_on'] = 'created'
|
||||
kw['sort_order'] = 'descending'
|
||||
print 'kw:', kw
|
||||
catalog = getToolByName(self.context, 'portal_catalog')
|
||||
return catalog(**kw)
|
||||
|
||||
def get_batchlinkparams(self):
|
||||
d = dict()
|
||||
for key in self.request.form:
|
||||
d[key] = self.request.form[key]
|
||||
if type(d[key]) is str:
|
||||
d[key] = unicode(d[key], 'utf-8').encode('utf-8')
|
||||
elif type(d[key]) is unicode:
|
||||
d[key] = d[key].encode('utf-8')
|
||||
return d
|
||||
|
|
|
@ -8,7 +8,7 @@ from ZODB.POSException import ConflictError
|
|||
from themis.fields.vocabs import ContactsSource
|
||||
|
||||
@indexer(plone.dexterity.interfaces.IDexterityItem)
|
||||
def dateIndexer(obj):
|
||||
def mailDateIndexer(obj):
|
||||
if obj.portal_type not in ('courrier_entrant', 'courrier_sortant'):
|
||||
return None
|
||||
for attr in ('date_reelle_courrier', 'date_reception'):
|
||||
|
@ -18,20 +18,36 @@ def dateIndexer(obj):
|
|||
return DateTime(getattr(obj, attr).isoformat())
|
||||
return None
|
||||
|
||||
grok.global_adapter(dateIndexer, name="mailDate")
|
||||
|
||||
grok.global_adapter(mailDateIndexer, name="mailDate")
|
||||
|
||||
@indexer(plone.dexterity.interfaces.IDexterityItem)
|
||||
def mail_dynamic_searchable_text_indexer(obj):
|
||||
"""Dynamic searchable text indexer.
|
||||
"""
|
||||
if obj.portal_type not in ('courrier_entrant', 'courrier_sortant'):
|
||||
def docDateIndexer(obj):
|
||||
if not '(D)' in obj.Type():
|
||||
return None
|
||||
for attr in ('date_document_imprime', 'date_du_document',
|
||||
'date_de_publication', 'date_du_rapport'):
|
||||
if not hasattr(obj, attr):
|
||||
continue
|
||||
if getattr(obj, attr):
|
||||
return DateTime(getattr(obj, attr).isoformat())
|
||||
return None
|
||||
grok.global_adapter(docDateIndexer, name="docDate")
|
||||
|
||||
data = obj.fichier
|
||||
if not data or data.getSize() == 0:
|
||||
return obj.title
|
||||
@indexer(plone.dexterity.interfaces.IDexterityItem)
|
||||
def docMeetingDateIndexer(obj):
|
||||
if not '(D)' in obj.Type():
|
||||
return None
|
||||
for attr in ('date_seance', 'date_de_la_commission',
|
||||
'date_seance_ou_commission'):
|
||||
if not hasattr(obj, attr):
|
||||
continue
|
||||
if getattr(obj, attr):
|
||||
return DateTime(getattr(obj, attr).isoformat())
|
||||
return None
|
||||
grok.global_adapter(docMeetingDateIndexer, name="docMeetingDate")
|
||||
|
||||
|
||||
def get_data_to_index(obj, data):
|
||||
# if there is no path to text/plain, do nothing
|
||||
transforms = getToolByName(obj, 'portal_transforms')
|
||||
if not transforms._findPath(data.contentType, 'text/plain'):
|
||||
|
@ -54,11 +70,47 @@ def mail_dynamic_searchable_text_indexer(obj):
|
|||
except UnicodeDecodeError:
|
||||
# ok, forget it
|
||||
data = ''
|
||||
return data
|
||||
|
||||
return obj.title + ' ' + data
|
||||
|
||||
@indexer(plone.dexterity.interfaces.IDexterityItem)
|
||||
def mail_dynamic_searchable_text_indexer(obj):
|
||||
"""Dynamic searchable text indexer.
|
||||
"""
|
||||
if obj.portal_type not in ('courrier_entrant', 'courrier_sortant'):
|
||||
return None
|
||||
|
||||
data = obj.fichier
|
||||
if not data or data.getSize() == 0:
|
||||
return obj.title
|
||||
|
||||
return obj.title + ' ' + get_data_to_index(obj, data)
|
||||
|
||||
grok.global_adapter(mail_dynamic_searchable_text_indexer, name='MailSearchableText')
|
||||
|
||||
|
||||
@indexer(plone.dexterity.interfaces.IDexterityItem)
|
||||
def doc_dynamic_searchable_text_indexer(obj):
|
||||
"""Dynamic searchable text indexer.
|
||||
"""
|
||||
if not '(D)' in obj.Type():
|
||||
return None
|
||||
|
||||
data = None
|
||||
for attr in ('fichier' 'document_imprime'):
|
||||
if not hasattr(obj, attr):
|
||||
continue
|
||||
if getattr(obj, attr):
|
||||
data = getattr(obj, attr)
|
||||
break
|
||||
if not data or data.getSize() == 0:
|
||||
return obj.title
|
||||
|
||||
return obj.title + ' ' + get_data_to_index(obj, data)
|
||||
|
||||
grok.global_adapter(doc_dynamic_searchable_text_indexer, name='DocSearchableText')
|
||||
|
||||
|
||||
@indexer(plone.dexterity.interfaces.IDexterityItem)
|
||||
def contactIndexer(obj):
|
||||
if obj.portal_type not in ('courrier_entrant', 'courrier_sortant'):
|
||||
|
@ -99,3 +151,89 @@ def contactFuzzyIndexer(obj):
|
|||
|
||||
grok.global_adapter(contactFuzzyIndexer, name="MailContactFuzzy")
|
||||
|
||||
|
||||
@indexer(plone.dexterity.interfaces.IDexterityItem)
|
||||
def docNumberIndexer(obj):
|
||||
if not '(D)' in obj.Type():
|
||||
return None
|
||||
for attr in ('numero_biq', 'numero_document', 'numero_bqr'):
|
||||
if not hasattr(obj, attr):
|
||||
continue
|
||||
if getattr(obj, attr):
|
||||
return getattr(obj, attr)
|
||||
return None
|
||||
grok.global_adapter(docNumberIndexer, name="docNumber")
|
||||
|
||||
|
||||
@indexer(plone.dexterity.interfaces.IDexterityItem)
|
||||
def docCommissionsIndexer(obj):
|
||||
if not '(D)' in obj.Type():
|
||||
return None
|
||||
l = []
|
||||
for attr in ('commissions', 'commission_qui_examine', 'commission',
|
||||
'examine_en', 'commisions__examine'):
|
||||
if not hasattr(obj, attr):
|
||||
continue
|
||||
t = getattr(obj, attr)
|
||||
if t:
|
||||
if type(t) is list:
|
||||
l.extend(t)
|
||||
elif type(t) in (unicode, str):
|
||||
l.append(t)
|
||||
if not l:
|
||||
return None
|
||||
return l
|
||||
grok.global_adapter(docCommissionsIndexer, name="docCommissions")
|
||||
|
||||
|
||||
@indexer(plone.dexterity.interfaces.IDexterityItem)
|
||||
def docCategoryIndexer(obj):
|
||||
if not '(D)' in obj.Type():
|
||||
return None
|
||||
category = [obj.Type().replace('(D)', '').strip()]
|
||||
if hasattr(obj, 'type_de_projet') and getattr(obj, 'type_de_projet'):
|
||||
s = getattr(obj, 'type_de_projet')
|
||||
if type(s) is list:
|
||||
category.extend(s)
|
||||
elif s:
|
||||
category.append(s)
|
||||
if hasattr(obj, 'type_de_proposition') and getattr(obj, 'type_de_proposition'):
|
||||
s = getattr(obj, 'type_de_proposition')
|
||||
if type(s) is list:
|
||||
category.extend(s)
|
||||
elif s:
|
||||
category.append(s)
|
||||
return category
|
||||
grok.global_adapter(docCategoryIndexer, name="docCategory")
|
||||
|
||||
@indexer(plone.dexterity.interfaces.IDexterityItem)
|
||||
def personsFuzzyIndexer(obj):
|
||||
if not '(D)' in obj.Type():
|
||||
return None
|
||||
persons = []
|
||||
src = ContactsSource()
|
||||
for attr in ('auteur', 'auteurs', 'rapporteurs', 'orateurs_seance',
|
||||
'orateurs', 'orateurs_en_commission', 'orateurs__en_seanceprop',
|
||||
'orateurs_rapportcom', 'orateurs_seance_reponse_orale',
|
||||
'ministres_concernes'):
|
||||
if not hasattr(obj, attr):
|
||||
continue
|
||||
value = getattr(obj, attr)
|
||||
if not value:
|
||||
continue
|
||||
if type(value) is not list:
|
||||
value = [value]
|
||||
for item in value:
|
||||
if ':' in item:
|
||||
try:
|
||||
r = src.fastGetTitleByToken(obj, item)
|
||||
except KeyError:
|
||||
continue
|
||||
if not type(r) is unicode:
|
||||
r = unicode(r, 'utf-8')
|
||||
persons.append(r)
|
||||
else:
|
||||
persons.append(item)
|
||||
return ' '.join(persons)
|
||||
|
||||
grok.global_adapter(personsFuzzyIndexer, name='DocPersonsFuzzy')
|
||||
|
|
|
@ -30,5 +30,39 @@
|
|||
<extra name="index_type" value="Okapi BM25 Rank"/>
|
||||
<extra name="lexicon_id" value="themis_lexicon"/>
|
||||
</index>
|
||||
<index name="docSearchableText" meta_type="ZCTextIndex">
|
||||
<indexed_attr value="DocSearchableText"/>
|
||||
<extra name="index_type" value="Okapi BM25 Rank"/>
|
||||
<extra name="lexicon_id" value="themis_lexicon"/>
|
||||
</index>
|
||||
<index name="docDate" meta_type="DateIndex">
|
||||
<indexed_attr value="docDate"/>
|
||||
</index>
|
||||
<index name="docMeetingDate" meta_type="DateIndex">
|
||||
<indexed_attr value="docMeetingDate"/>
|
||||
</index>
|
||||
<index name="docNumber" meta_type="FieldIndex">
|
||||
<indexed_attr value="docNumber"/>
|
||||
</index>
|
||||
<index name="docCategory" meta_type="KeywordIndex">
|
||||
<indexed_attr value="docCategory"/>
|
||||
</index>
|
||||
<index name="docTopics" meta_type="KeywordIndex">
|
||||
<indexed_attr value="matieres"/>
|
||||
</index>
|
||||
<index name="docPersonsFuzzy" meta_type="ZCTextIndex">
|
||||
<indexed_attr value="DocPersonsFuzzy"/>
|
||||
<extra name="index_type" value="Okapi BM25 Rank"/>
|
||||
<extra name="lexicon_id" value="themis_lexicon"/>
|
||||
</index>
|
||||
<index name="docCommissionState" meta_type="FieldIndex">
|
||||
<indexed_attr value="etat_en_commission"/>
|
||||
</index>
|
||||
<index name="docCommissions" meta_type="KeywordIndex">
|
||||
<indexed_attr value="docCommissions"/>
|
||||
</index>
|
||||
<column value="mailDate"/>
|
||||
<column value="docDate"/>
|
||||
<column value="docMeetingDate"/>
|
||||
<column value="docNumber"/>
|
||||
</object>
|
||||
|
|
Reference in New Issue