add document search

This commit is contained in:
Frédéric Péters 2011-12-07 17:31:55 +01:00
parent fbb648d366
commit 806e372068
5 changed files with 456 additions and 12 deletions

View File

@ -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"

87
themis/search/doc.pt Normal file
View File

@ -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>

View File

@ -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

View File

@ -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')

View File

@ -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>