From 3dc65a977069976cda71833130cb7b3e54173ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Tue, 6 Dec 2011 17:17:15 +0100 Subject: [PATCH] add mail search --- themis/search/batch_macros.pt | 167 +++++++++++++++++++++ themis/search/configure.zcml | 28 +++- themis/search/contact-input.pt | 39 +++++ themis/search/doc.py | 5 + themis/search/indexer.py | 108 +++++++++++++ themis/search/interfaces.py | 4 + themis/search/mail.pt | 69 +++++++++ themis/search/mail.py | 148 ++++++++++++++++++ themis/search/profiles/default/catalog.xml | 34 +++++ themis/search/view_form.pt | 8 + 10 files changed, 607 insertions(+), 3 deletions(-) create mode 100644 themis/search/batch_macros.pt create mode 100644 themis/search/contact-input.pt create mode 100644 themis/search/doc.py create mode 100644 themis/search/indexer.py create mode 100644 themis/search/interfaces.py create mode 100644 themis/search/mail.pt create mode 100644 themis/search/mail.py create mode 100644 themis/search/profiles/default/catalog.xml create mode 100644 themis/search/view_form.pt diff --git a/themis/search/batch_macros.pt b/themis/search/batch_macros.pt new file mode 100644 index 0000000..2f7b213 --- /dev/null +++ b/themis/search/batch_macros.pt @@ -0,0 +1,167 @@ + + + +You can feed in batch_base_url by enclosing +the metal:use-macro="context/batch_macros/macros/navigation" statement in your +template with a tal:define="batch_base_url YOUR_BASE_URL" tales expression. + +
+ + + + « + + Previous + n + items + + + + « + + Previous + n + items + + + + + + + + Next + n + items + + » + + + + Next + n + items + + » + + + + Page : + + + Link to first + + + 1 + + … + + + + + Pagelist with quantum leap links to previous pages for quick navigation + + + + + … + + + + Pagelist with links to previous pages for quick navigation + + + + + + + + Current page + + Current page number + + + Pagelist with links to next pages for quick navigation + + + + + + + + Pagelist with quantum leap links to next pages for quick navigation + + + … + + + + + + Link to last + + + + … + + 3457 + + +
+ +
+Abonnez-vous au flux de cette recherche +
+ + + diff --git a/themis/search/configure.zcml b/themis/search/configure.zcml index fc473ec..b6b7838 100644 --- a/themis/search/configure.zcml +++ b/themis/search/configure.zcml @@ -1,11 +1,33 @@ - + + + + + + + + + + + - - diff --git a/themis/search/contact-input.pt b/themis/search/contact-input.pt new file mode 100644 index 0000000..c98a715 --- /dev/null +++ b/themis/search/contact-input.pt @@ -0,0 +1,39 @@ + +
+ + diff --git a/themis/search/doc.py b/themis/search/doc.py new file mode 100644 index 0000000..9c98e9c --- /dev/null +++ b/themis/search/doc.py @@ -0,0 +1,5 @@ +from Products.Five import BrowserView + + +class SearchView(BrowserView): + pass diff --git a/themis/search/indexer.py b/themis/search/indexer.py new file mode 100644 index 0000000..6845ade --- /dev/null +++ b/themis/search/indexer.py @@ -0,0 +1,108 @@ +from DateTime import DateTime +from plone.indexer import indexer +from five import grok +import plone.dexterity.interfaces +from Products.CMFCore.utils import getToolByName +from ZODB.POSException import ConflictError + +from themis.fields.vocabs import ContactsSource + +@indexer(plone.dexterity.interfaces.IDexterityItem) +def dateIndexer(obj): + if obj.portal_type not in ('courrier_entrant', 'courrier_sortant'): + return None + for attr in ('date_reelle_courrier', 'date_reception'): + if not hasattr(obj, attr): + continue + if getattr(obj, attr): + return DateTime(getattr(obj, attr).isoformat()) + return None + +grok.global_adapter(dateIndexer, name="dateCourrier") + + +@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 + + # if there is no path to text/plain, do nothing + transforms = getToolByName(obj, 'portal_transforms') + if not transforms._findPath(data.contentType, 'text/plain'): + return obj.title + + # convert it to text/plain + try: + datastream = transforms.convertTo( + 'text/plain', data.data, mimetype=data.contentType, + filename=data.filename) + data = datastream.getData() + except (ConflictError, KeyboardInterrupt): + raise + + try: + data = unicode(datastream.getData(), 'utf-8') + except UnicodeDecodeError, e: + try: + data = unicode(datastream.getData()[:e.start], 'utf-8') + except UnicodeDecodeError: + # ok, forget it + data = '' + + return obj.title + ' ' + data + +grok.global_adapter(mail_dynamic_searchable_text_indexer, name='MailSearchableText') + + +@indexer(plone.dexterity.interfaces.IDexterityItem) +def contactIndexer(obj): + if obj.portal_type not in ('courrier_entrant', 'courrier_sortant'): + return None + contacts_dir = getattr(getToolByName(obj, 'portal_url').getPortalObject(), 'contacts') + for attr in ('expediteur', 'destinataire'): + if not hasattr(obj, attr): + continue + v = getattr(obj, attr) + if not v: + continue + v = v[0] + if ':' in v and v.split(':')[0] in ('deputy', 'ministry', 'ministry-collab'): + return v + elif ':' in v and v.split(':')[0] == 'contact': + contact_id = v.split(':')[1] + if not hasattr(contacts_dir, contact_id): + return contact_id.replace('-', ' ') + else: + return getattr(contacts_dir, contact_id).title + else: + return v + return None + +grok.global_adapter(contactIndexer, name="MailContact") + +@indexer(plone.dexterity.interfaces.IDexterityItem) +def contactFuzzyIndexer(obj): + if obj.portal_type not in ('courrier_entrant', 'courrier_sortant'): + return None + for attr in ('expediteur', 'destinataire'): + if not hasattr(obj, attr): + continue + v = getattr(obj, attr) + if not v: + continue + v = v[0] + if ':' in v: + src = ContactsSource() + return src(obj).getTermByToken(v).title + else: + return v + return None + +grok.global_adapter(contactFuzzyIndexer, name="MailContactFuzzy") + diff --git a/themis/search/interfaces.py b/themis/search/interfaces.py new file mode 100644 index 0000000..4eadd8f --- /dev/null +++ b/themis/search/interfaces.py @@ -0,0 +1,4 @@ +import zope.i18nmessageid +MessageFactory = zope.i18nmessageid.MessageFactory('themis.search') + + diff --git a/themis/search/mail.pt b/themis/search/mail.pt new file mode 100644 index 0000000..16e029d --- /dev/null +++ b/themis/search/mail.pt @@ -0,0 +1,69 @@ + + + + + + + +

Recherche de courrier

+ +
+
+ + +
+
+ Nombre de résultats : +
+ +
+
+ + + + + + + +
+
+ + +
+
+
+ + + + + + + diff --git a/themis/search/mail.py b/themis/search/mail.py new file mode 100644 index 0000000..ac62d77 --- /dev/null +++ b/themis/search/mail.py @@ -0,0 +1,148 @@ +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.text import TextWidget +from z3c.form.interfaces import ITextWidget +import z3c.form.interfaces + +from themis.fields.vocabs import get_terms_for_persons + +from interfaces import MessageFactory as _ + +def FieldRadioboxesWidget(field, request): + return widget.FieldWidget(field, RadioWidget(request)) + +class IContactWidget(ITextWidget): + pass + +class ContactWidget(TextWidget): + implements(IContactWidget) + input_template = ViewPageTemplateFile('contact-input.pt') + + def render(self): + if self.mode == z3c.form.interfaces.INPUT_MODE: + return self.input_template(self) + return TextWidget.render(self) + + def extract(self, default=z3c.form.interfaces.NO_VALUE): + t = self.request.get(self.name, default) + if not t: + t = self.request.get(self.name + '-select', default) + return t + + def get_known_contacts(self): + terms = get_terms_for_persons(self.context, include_deputies=True, + include_ministries=True, include_ministries_collaborators=True) + return terms + + +def FieldContactWidget(field, request): + return widget.FieldWidget(field, ContactWidget(request)) + +@grok.provider(IContextSourceBinder) +def possible_scopes(context): + return SimpleVocabulary([ + SimpleVocabulary.createTerm('all', 'all', _(u'All Mails')), + SimpleVocabulary.createTerm('incoming', 'incoming', _(u'Incoming Mails')), + SimpleVocabulary.createTerm('outgoing', 'outgoing', _(u'Outgoing Mails'))]) + +@grok.provider(IContextSourceBinder) +def possible_categories(context): + portal = getToolByName(context, 'portal_url').getPortalObject() + fti = getattr(portal.portal_types, 'courrier_entrant') + return fti.lookupSchema().get('categorie_de_courrier').value_type.vocabulary + +@grok.provider(IContextSourceBinder) +def possible_subcategories(context): + portal = getToolByName(context, 'portal_url').getPortalObject() + fti = getattr(portal.portal_types, 'courrier_entrant') + return fti.lookupSchema().get('sous_categorie_de_courrier').value_type.vocabulary + +class IMailSearchForm(interface.Interface): + text = schema.TextLine(title=_(u'Text'), required=False) + date = schema.Date(title=_(u'Date'), required=False) + number = schema.TextLine(title=_(u'Mail Number'), required=False) + category = schema.Choice(title=_(u'Category'), required=False, + source=possible_categories) + subcategory = schema.Choice(title=_(u'Subcategory'), required=False, + source=possible_subcategories) + scope = schema.Choice(title=_(u'Scope'), required=False, + source=possible_scopes, default='all') + contact = schema.TextLine(title=_(u'Contact'), required=False) + +class MailSearchForm(form.Form): + fields = field.Fields(IMailSearchForm) + fields['scope'].widgetFactory = FieldRadioboxesWidget + fields['contact'].widgetFactory = FieldContactWidget + ignoreContext = True + template = ViewPageTemplateFile('view_form.pt') + + @button.buttonAndHandler(_(u'Submit')) + def handleApply(self, action): + pass + + +class SearchView(BrowserView): + batch_macros = ViewPageTemplateFile('batch_macros.pt') + + def form(self): + f = MailSearchForm(self.context, self.request) + f.update() + return f.render() + + def search_results(self): + f = MailSearchForm(self.context, self.request) + f.update() + + data, errors = f.extractData() + kw = {} + if not data.get('scope'): + return [] + + if data.get('date'): + kw['dateCourrier'] = {'query': [data.get('date'), data.get('date')], 'range': 'minmax'} + if data.get('text'): + kw['MailSearchableText'] = data.get('text') + if data.get('number'): + kw['numero_courrier'] = data.get('number') + if data.get('category'): + kw['categorie_de_courrier'] = data.get('category') + if data.get('subcategory'): + kw['sous_categorie_de_courrier'] = data.get('subcategory') + if data.get('contact'): + v = data.get('contact') + if ':' in v and v.split(':')[0] in ('deputy', 'ministry', 'ministry-collab'): + kw['MailContact'] = v + else: + kw['MailContactFuzzy'] = v + + if data.get('scope') == 'all': + kw['portal_type'] = ['courrier_entrant', 'courrier_sortant'] + elif data.get('scope') == 'incoming': + kw['portal_type'] = ['courrier_entrant'] + elif data.get('scope') == 'outgoing': + kw['portal_type'] = ['courrier_sortant'] + + kw['sort_on'] = 'dateCourrier' + 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 + diff --git a/themis/search/profiles/default/catalog.xml b/themis/search/profiles/default/catalog.xml new file mode 100644 index 0000000..08dd872 --- /dev/null +++ b/themis/search/profiles/default/catalog.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/themis/search/view_form.pt b/themis/search/view_form.pt new file mode 100644 index 0000000..824f8fe --- /dev/null +++ b/themis/search/view_form.pt @@ -0,0 +1,8 @@ + + + + + + +