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

Yvo Schubbe <[email protected]>
Newsgroups gmane.comp.web.zope.cmf.cvs
Message-ID <[email protected]>
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.