[Checkins] SVN: CMF/branches/yuppie-formlib/CMFDefault/formlib/ - added some base classes and utilities for using zope.formlib in CMF

Yvo Schubbe y.2006_ at wcm-solutions.de
Thu Nov 2 13:08:20 EST 2006


Log message for revision 71029:
  - added some base classes and utilities for using zope.formlib in CMF

Changed:
  A   CMF/branches/yuppie-formlib/CMFDefault/formlib/
  A   CMF/branches/yuppie-formlib/CMFDefault/formlib/__init__.py
  A   CMF/branches/yuppie-formlib/CMFDefault/formlib/editform.pt
  A   CMF/branches/yuppie-formlib/CMFDefault/formlib/form.py
  A   CMF/branches/yuppie-formlib/CMFDefault/formlib/schema.py
  A   CMF/branches/yuppie-formlib/CMFDefault/formlib/schema.txt
  A   CMF/branches/yuppie-formlib/CMFDefault/formlib/tests.py
  A   CMF/branches/yuppie-formlib/CMFDefault/formlib/viewform.pt
  A   CMF/branches/yuppie-formlib/CMFDefault/formlib/vocabulary.py

-=-
Added: CMF/branches/yuppie-formlib/CMFDefault/formlib/__init__.py
===================================================================
--- CMF/branches/yuppie-formlib/CMFDefault/formlib/__init__.py	2006-11-02 17:38:30 UTC (rev 71028)
+++ CMF/branches/yuppie-formlib/CMFDefault/formlib/__init__.py	2006-11-02 18:08:20 UTC (rev 71029)
@@ -0,0 +1,16 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""CMFDefault formlib support.
+
+$Id$
+"""


Property changes on: CMF/branches/yuppie-formlib/CMFDefault/formlib/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: CMF/branches/yuppie-formlib/CMFDefault/formlib/editform.pt
===================================================================
--- CMF/branches/yuppie-formlib/CMFDefault/formlib/editform.pt	2006-11-02 17:38:30 UTC (rev 71028)
+++ CMF/branches/yuppie-formlib/CMFDefault/formlib/editform.pt	2006-11-02 18:08:20 UTC (rev 71029)
@@ -0,0 +1,38 @@
+<html metal:use-macro="context/@@standard_macros/page">
+<body>
+
+<metal:slot metal:fill-slot="body" i18n:domain="cmf_default">
+<ul class="errors" tal:condition="view/errors">
+ <li tal:repeat="error view/error_views"><tal:span
+     tal:replace="structure error" /></li>
+</ul>
+
+<h1 tal:content="view/label">EDIT: OBJECT</h1>
+
+<form class="form" action="." method="post" enctype="multipart/form-data"
+   tal:attributes="action request/ACTUAL_URL">
+<div class="widgets">
+ <tal:loop tal:repeat="widget view/widgets"
+><div class="widget"
+    tal:define="split widget/split|nothing; hint widget/hint|nothing"
+    tal:attributes="class python: split and 'widget split' or 'widget'">
+  <label tal:attributes="for widget/name; title python: hint or None"
+  ><span class="required" tal:condition="widget/required">*</span
+  ><tal:span tal:content="widget/label" /></label>
+  <div class="field">
+   <div class="data"><tal:span tal:replace="structure widget" /></div
+  ><tal:case tal:condition="widget/error"
+      tal:replace="structure widget/error" /></div>
+ </div></tal:loop>
+</div>
+<div class="clear"></div>
+
+<div class="buttons">
+ <tal:loop tal:repeat="action view/actions"
+    tal:replace="structure action/render" />
+</div>
+</form>
+</metal:slot>
+
+</body>
+</html>


Property changes on: CMF/branches/yuppie-formlib/CMFDefault/formlib/editform.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: CMF/branches/yuppie-formlib/CMFDefault/formlib/form.py
===================================================================
--- CMF/branches/yuppie-formlib/CMFDefault/formlib/form.py	2006-11-02 17:38:30 UTC (rev 71028)
+++ CMF/branches/yuppie-formlib/CMFDefault/formlib/form.py	2006-11-02 18:08:20 UTC (rev 71029)
@@ -0,0 +1,166 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Formlib form base classes.
+
+$Id$
+"""
+
+from datetime import datetime
+from sets import Set
+
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+from Products.Five.formlib.formbase import PageDisplayForm
+from Products.Five.formlib.formbase import PageForm
+from Products.PluginIndexes.DateIndex.DateIndex import Local
+from zope.formlib import form
+from zope.i18n.interfaces import IUserPreferredLanguages
+from zope.i18n.locales import LoadLocaleError
+from zope.i18n.locales import locales
+from ZTUtils import make_query
+
+from Products.CMFDefault.utils import Message as _
+from Products.CMFDefault.utils import translate
+from Products.CMFDefault.browser.utils import ViewBase
+
+
+# from zope.publisher.http.HTTPRequest
+def getLocale(request):
+    envadapter = IUserPreferredLanguages(request, None)
+    if envadapter is None:
+        return None
+
+    langs = envadapter.getPreferredLanguages()
+    for httplang in langs:
+        parts = (httplang.split('-') + [None, None])[:3]
+        try:
+            return locales.getLocale(*parts)
+        except LoadLocaleError:
+            # Just try the next combination
+            pass
+    else:
+        # No combination gave us an existing locale, so use the default,
+        # which is guaranteed to exist
+        return locales.getLocale(None, None, None)
+
+
+class EditFormBase(PageForm, ViewBase):
+
+    template = ViewPageTemplateFile('editform.pt')
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        self.request.locale = getLocale(request)
+
+    def _setRedirect(self, provider_id, action_path, keys=''):
+        provider = self._getTool(provider_id)
+        try:
+            target = provider.getActionInfo(action_path, self.context)['url']
+        except ValueError:
+            target = self._getPortalURL()
+
+        kw = {}
+        if self.status:
+            message = translate(self.status, self.context)
+            if isinstance(message, unicode):
+                message = message.encode(self._getBrowserCharset())
+            kw['portal_status_message'] = message
+        for k in keys.split(','):
+            k = k.strip()
+            v = self.request.form.get(k, None)
+            if v:
+                kw[k] = v
+
+        query = kw and ( '?%s' % make_query(kw) ) or ''
+        self.request.RESPONSE.redirect( '%s%s' % (target, query) )
+
+        return ''
+
+    def handle_failure(self, action, data, errors):
+        if self.status:
+            message = translate(self.status, self.context)
+            self.request.other['portal_status_message'] = message
+
+
+class ContentEditFormBase(EditFormBase):
+
+    actions = form.Actions(
+        form.Action(
+            name='change',
+            label=_(u'Change'),
+            validator='handle_validate',
+            success='handle_change_success',
+            failure='handle_failure'),
+        form.Action(
+            name='change_and_view',
+            label=_(u'Change and View'),
+            validator='handle_validate',
+            success='handle_change_and_view_success',
+            failure='handle_failure'))
+
+    description = u''
+
+    def setUpWidgets(self, ignore_request=False):
+        self.adapters = {}
+        self.widgets = form.setUpEditWidgets(
+            self.form_fields, self.prefix, self.context, self.request,
+            adapters=self.adapters, ignore_request=ignore_request
+            )
+
+    @property
+    def label(self):
+        obj_type = translate(self.context.Type(), self.context)
+        return _(u'Edit ${obj_type}', mapping={'obj_type': obj_type})
+
+    def handle_validate(self, action, data):
+        if self.context.wl_isLocked():
+            self.status = _(u'This resource is locked via webDAV.')
+            return ()
+
+    def _handle_success(self, action, data):
+        # normalize set and datetime
+        for k, v in data.iteritems():
+            if isinstance(v, Set):
+                data[k] = set(v)
+            elif isinstance(v, datetime) and v.tzname() is None:
+                data[k] = v.replace(tzinfo=Local)
+        if form.applyChanges(self.context, self.form_fields, data,
+                             self.adapters):
+            self.context.reindexObject()
+            obj_type = translate(self.context.Type(), self.context)
+            self.status = _(u'${obj_type} changed.',
+                            mapping={'obj_type': obj_type})
+        else:
+            self.status = _(u'Nothing to change.')
+
+    def handle_change_success(self, action, data):
+        self._handle_success(action, data)
+        return self._setRedirect('portal_types', 'object/edit')
+
+    def handle_change_and_view_success(self, action, data):
+        self._handle_success(action, data)
+        return self._setRedirect('portal_types', 'object/view')
+
+
+class DisplayFormBase(PageDisplayForm, ViewBase):
+
+    template = ViewPageTemplateFile('viewform.pt')
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        self.request.locale = getLocale(request)
+
+    @property
+    def label(self):
+        return self.context.Type()


Property changes on: CMF/branches/yuppie-formlib/CMFDefault/formlib/form.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: CMF/branches/yuppie-formlib/CMFDefault/formlib/schema.py
===================================================================
--- CMF/branches/yuppie-formlib/CMFDefault/formlib/schema.py	2006-11-02 17:38:30 UTC (rev 71028)
+++ CMF/branches/yuppie-formlib/CMFDefault/formlib/schema.py	2006-11-02 18:08:20 UTC (rev 71029)
@@ -0,0 +1,125 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Formlib schema adapter base classes.
+
+$Id$
+"""
+
+from datetime import datetime
+
+from DateTime.DateTime import DateTime
+from Products.PluginIndexes.DateIndex.DateIndex import Local
+from zope.interface import implements
+from zope.schema import BytesLine
+from zope.schema.interfaces import IBytesLine
+
+from Products.CMFCore.utils import getToolByName
+from Products.CMFDefault.utils import checkEmailAddress
+
+
+class SchemaAdapterBase(object):
+
+    def __init__(self, context):
+        self.context = context
+        ptool = getToolByName(context, 'portal_properties')
+        self.encoding = ptool.getProperty('default_charset', None)
+
+
+_marker = object()
+
+class ProxyFieldProperty(object):
+
+    """Computed attributes based on schema fields.
+
+    Based on zope.schema.fieldproperty.FieldProperty.
+    """
+
+    def __init__(self, field, get_name=None, set_name=None):
+        if get_name is None:
+            get_name = field.__name__
+
+        self._field = field
+        self._get_name = get_name
+        self._set_name = set_name
+
+    def __get__(self, inst, klass):
+        if inst is None:
+            return self
+
+        attribute = getattr(inst.context, self._get_name, _marker)
+        if attribute is _marker:
+            field = self._field.bind(inst)
+            attribute = getattr(field, 'default', _marker)
+            if attribute is _marker:
+                raise AttributeError(self._field.__name__)
+        elif callable(attribute):
+            attribute = attribute()
+
+        if isinstance(attribute, str) and inst.encoding:
+            return attribute.decode(inst.encoding)
+        elif isinstance(attribute, DateTime):
+            return datetime.fromtimestamp(attribute.timeTime(), Local)
+        elif isinstance(attribute, (tuple, list)):
+            if inst.encoding:
+                attribute = [ isinstance(v, str)
+                              and v.decode(inst.encoding) or v
+                              for v in attribute ]
+            return set(attribute)
+        return attribute
+
+    def __set__(self, inst, value):
+        field = self._field.bind(inst)
+        field.validate(value)
+        if field.readonly:
+            raise ValueError(self._field.__name__, 'field is readonly')
+        if isinstance(value, unicode) and inst.encoding:
+            value = value.encode(inst.encoding)
+        elif isinstance(value, datetime):
+            value = DateTime(*value.astimezone(Local).timetuple()[:6])
+        elif isinstance(value, set):
+            if inst.encoding:
+                value = [ isinstance(v, unicode)
+                          and v.encode(inst.encoding) or v
+                          for v in value ]
+            value = tuple(value)
+        if self._set_name:
+            getattr(inst.context, self._set_name)(value)
+        elif inst.context.hasProperty(self._get_name):
+            inst.context._updateProperty(self._get_name, value)
+        else:
+            setattr(inst.context, self._get_name, value)
+
+    def __getattr__(self, name):
+        return getattr(self._field, name)
+
+
+class IEmailLine(IBytesLine):
+
+    """A field containing an email address.
+    """
+
+
+class EmailLine(BytesLine):
+
+    """Email schema field.
+    """
+
+    implements(IEmailLine)
+
+    default = ''
+    missing_value = ''
+
+    def _validate(self, value):
+        super(EmailLine, self)._validate(value)
+        checkEmailAddress(value)
+        return True


Property changes on: CMF/branches/yuppie-formlib/CMFDefault/formlib/schema.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: CMF/branches/yuppie-formlib/CMFDefault/formlib/schema.txt
===================================================================
--- CMF/branches/yuppie-formlib/CMFDefault/formlib/schema.txt	2006-11-02 17:38:30 UTC (rev 71028)
+++ CMF/branches/yuppie-formlib/CMFDefault/formlib/schema.txt	2006-11-02 18:08:20 UTC (rev 71029)
@@ -0,0 +1,76 @@
+Utilities
+
+
+  Testing ProxyFieldProperty:
+
+    Setting up content and adapter::
+
+      >>> from OFS.PropertyManager import PropertyManager
+      >>> class FooContent(PropertyManager):
+      ...     _properties=({'id':'foo_prop', 'type': 'string'},)
+      ...     foo_text = ''
+      ...     foo_datetime = None
+      ...     foo_set = ()
+      ...     foo_prop = ''
+
+      >>> from zope.interface import Interface
+      >>> from zope import schema
+      >>> class IFooContentView(Interface):
+      ...     foo_text = schema.Text()
+      ...     foo_datetime = schema.Datetime()
+      ...     foo_set = schema.Set()
+      ...     foo_prop = schema.Text()
+
+      >>> from Products.CMFDefault.formlib.schema import ProxyFieldProperty
+      >>> class FooContentAdapter(object):
+      ... 
+      ...     foo_text = ProxyFieldProperty(IFooContentView['foo_text'])
+      ...     foo_datetime = ProxyFieldProperty(IFooContentView['foo_datetime'])
+      ...     foo_set = ProxyFieldProperty(IFooContentView['foo_set'])
+      ...     foo_prop = ProxyFieldProperty(IFooContentView['foo_prop'])
+      ... 
+      ...     def __init__(self, context):
+      ...         self.context = context
+      ...         self.encoding = 'utf-8'
+
+      >>> content = FooContent()
+      >>> adapter = FooContentAdapter(content)
+
+    unicode is mapped to str::
+
+      >>> foo_text = u'foo'
+      >>> adapter.foo_text = foo_text
+      >>> content.foo_text
+      'foo'
+      >>> adapter.foo_text == foo_text
+      True
+
+    datetime is mapped to DateTime::
+
+      >>> from datetime import datetime
+      >>> from DateTime.DateTime import DateTime
+      >>> from Products.PluginIndexes.DateIndex.DateIndex import Local
+      >>> foo_datetime = datetime(2002, 2, 2, 2, 2, 2, tzinfo=Local)
+      >>> adapter.foo_datetime = foo_datetime
+      >>> isinstance(content.foo_datetime, DateTime)
+      True
+      >>> adapter.foo_datetime == foo_datetime
+      True
+
+    set is mapped to tuple::
+
+      >>> foo_set = set([3, 1, 4])
+      >>> adapter.foo_set = foo_set
+      >>> content.foo_set
+      (1, 3, 4)
+      >>> adapter.foo_set == foo_set
+      True
+
+    PropertyManager properties use _setProperty::
+
+      >>> foo_prop = u'foo'
+      >>> adapter.foo_prop = foo_text
+      >>> content.foo_prop
+      'foo'
+      >>> adapter.foo_prop == foo_prop
+      True


Property changes on: CMF/branches/yuppie-formlib/CMFDefault/formlib/schema.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: CMF/branches/yuppie-formlib/CMFDefault/formlib/tests.py
===================================================================
--- CMF/branches/yuppie-formlib/CMFDefault/formlib/tests.py	2006-11-02 17:38:30 UTC (rev 71028)
+++ CMF/branches/yuppie-formlib/CMFDefault/formlib/tests.py	2006-11-02 18:08:20 UTC (rev 71029)
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""CMFDefault formlib tests.
+
+$Id$
+"""
+
+import unittest
+import Testing
+from zope.testing import doctest
+
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('schema.txt',
+                             optionflags=doctest.NORMALIZE_WHITESPACE),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')


Property changes on: CMF/branches/yuppie-formlib/CMFDefault/formlib/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Added: CMF/branches/yuppie-formlib/CMFDefault/formlib/viewform.pt
===================================================================
--- CMF/branches/yuppie-formlib/CMFDefault/formlib/viewform.pt	2006-11-02 17:38:30 UTC (rev 71028)
+++ CMF/branches/yuppie-formlib/CMFDefault/formlib/viewform.pt	2006-11-02 18:08:20 UTC (rev 71029)
@@ -0,0 +1,22 @@
+<html metal:use-macro="context/@@standard_macros/page">
+<body>
+
+<metal:slot metal:fill-slot="body" i18n:domain="cmf_default">
+<h1 tal:content="view/label">TYPE</h1>
+
+<div class="form">
+<div class="widgets">
+ <tal:loop tal:repeat="widget view/widgets"
+><div class="widget" tal:define="split widget/split|nothing"
+    tal:attributes="class python: split and 'widget split' or 'widget'">
+  <label tal:content="widget/label">FIELD TITLE</label>
+  <div class="field">
+   <div class="data"><tal:span tal:replace="structure widget" /></div></div>
+ </div></tal:loop>
+</div>
+<div class="clear" />
+</div>
+</metal:slot>
+
+</body>
+</html>


Property changes on: CMF/branches/yuppie-formlib/CMFDefault/formlib/viewform.pt
___________________________________________________________________
Name: svn:eol-style
   + native

Added: CMF/branches/yuppie-formlib/CMFDefault/formlib/vocabulary.py
===================================================================
--- CMF/branches/yuppie-formlib/CMFDefault/formlib/vocabulary.py	2006-11-02 17:38:30 UTC (rev 71028)
+++ CMF/branches/yuppie-formlib/CMFDefault/formlib/vocabulary.py	2006-11-02 18:08:20 UTC (rev 71029)
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Formlib schema vocabulary base classes.
+
+$Id$
+"""
+
+from zope.schema.vocabulary import SimpleVocabulary
+
+
+class SimpleVocabulary(SimpleVocabulary):
+
+    def fromTitleItems(cls, items, *interfaces):
+        """Construct a vocabulary from a list of (token, value, title) tuples.
+        """
+        terms = [ cls.createTerm(value, token, title)
+                  for (token, value, title) in items ]
+        return cls(terms, *interfaces)
+
+    fromTitleItems = classmethod(fromTitleItems)


Property changes on: CMF/branches/yuppie-formlib/CMFDefault/formlib/vocabulary.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native



More information about the Checkins mailing list