Hi,

On Wed, Jun 14, 2006 at 02:41:05PM -0500, mats.nordgren wrote:
> Frank,
> 
> That would be great.  If you wish you can email it to me.
> 
> This should be included in the trunk IMHO.
> 
> Thanks,

I attached the widgets, zcml-statements to configure them and
modified Dict-implementation.

Good luck,

Frank
<configure xmlns:zope="http://namespaces.zope.org/zope";
        xmlns:i18n="http://namespaces.zope.org/i18n";
        xmlns="http://namespaces.zope.org/browser"; i18n_domain="mpgsite">

        <!-- Dictionary widget dispatcher -->
        <zope:view type="zope.publisher.interfaces.browser.IBrowserRequest"
                provides="zope.app.form.interfaces.IInputWidget"
                for="zope.schema.interfaces.IDict"
                factory="mpgsite.browser.widgets.DictionaryWidgetFactory"
                permission="zope.Public" />

        <!-- Choice()-keyed Dictionary Widget -->
        <zope:view type="zope.publisher.interfaces.browser.IBrowserRequest"
                provides="zope.app.form.interfaces.IInputWidget"
                for="zope.schema.interfaces.IDict 
zope.schema.interfaces.IChoice zope.schema.interfaces.IField"
                factory="mpgsite.browser.widgets.ChoicyDictionaryWidget"
                permission="zope.Public" />

        <!-- Arbitrary Dictionary Widget -->
        <zope:view type="zope.publisher.interfaces.browser.IBrowserRequest"
                provides="zope.app.form.interfaces.IInputWidget"
                for="zope.schema.interfaces.IDict zope.schema.interfaces.IField 
zope.schema.interfaces.IField"
                factory="mpgsite.browser.widgets.SimpleDictionaryWidget"
                permission="zope.Public" />
</configure>
from zope.app.form.browser import ObjectWidget, ListSequenceWidget
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from zope.interface import implements
from zope.app import zapi
from zope.app.form.browser.objectwidget import ObjectWidgetView, ObjectWidget
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from mpgsite.interfaces import IMpgSequenceField
from zope.app.form.browser.widget import BrowserWidget
from zope.app.form.interfaces import IDisplayWidget, IInputWidget
from zope.app.form import InputWidget
from zope.app.form.interfaces import WidgetInputError, MissingInputError
from zope.schema.interfaces import ValidationError, InvalidValue
from zope.app.i18n import MessageFactory
_=MessageFactory('mpgsite')
from zope.i18n import translate
from zope.schema.vocabulary import SimpleVocabulary, SimpleTerm
from zope.app.form.browser.widget import renderElement
from zope.app.form.interfaces import ConversionError
from zope.app.form.browser import TextWidget, SequenceDisplayWidget
from zope.security.proxy import removeSecurityProxy
import sys
from zope.schema import Object
from zope.annotation.interfaces import IAnnotations

class MpgTextWidget(TextWidget):
        def _toFieldValue(self, input):
                try:
                        value = unicode(input)
                except ValueError, v:
                        raise ConversionError(_("Invalid text data"), v)
                return value


class I18NTextLineWidget(MpgTextWidget):
   def __call__(self):
        value = self._getFormValue()
        if value is None or value == self.context.missing_value:
            value = ''

        kwargs = {'type': self.type,
                  'name': self.name,
                  'id': self.name,
                  'value': value,
                  'cssClass': self.cssClass,
                  'style': self.style,
                  'extra': self.extra}
        if self.displayMaxWidth:
            kwargs['maxlength'] = self.displayMaxWidth # TODO This is untested.

        return renderElement(self.tag, **kwargs)

class SimpleObjectWidget(ObjectWidget):
    """A Widget that shows all widgets of an object"""
    def __call__(self,context,request):
        xhtml=''
        for widget in context.subwidgets:
            xhtml +=widget()
        return xhtml


def ObjectInputWidgetDispatcher(context, request):
    """Dispatch widget for Object schema field to widget that is
    registered for (IObject, schema, IBrowserRequest) where schema
    is the schema of the object."""
    class Obj(object):
        implements(context.schema)

    widget=zapi.getMultiAdapter((context, Obj(), request), IInputWidget)
    return widget

class ObjectInputWidget(ObjectWidget):
        def getInputValue(self):
                errors = []
                content = self.factory()
                for name in self.names:
                        try:
                                setattr(content, name, 
self.getSubWidget(name).getInputValue())
                        except Exception, e:
                                errors.append(e)
                                if self._error is None:
                                        self._error = {}

                                if name not in self._error:
                                        self._error[name] = e

                # Don't raise errors when widget operations (add to list, 
remove element, ...) are processed
                if ( 'mpgsite.no_form_action' not in IAnnotations(self.request) 
) and errors:
                        raise errors[0]

                return content

def FileLocal(filename,depth):
    path='/'.join(sys._getframe(depth).f_globals['__file__'].split('/')[:-1])
    return path + '/' +  filename
    

class TemplateObjectWidget(ObjectWidget):
    """A Widget that uses a page template"""
    def __init__(self, context, request, factory, template_, **kw):
        super(TemplateObjectWidget, self).__init__(context, request, factory, 
**kw)
        class TemplateObjectWidgetView(ObjectWidgetView):
            template = ViewPageTemplateFile(template_)
        self.view = TemplateObjectWidgetView(self, request)

def TemplateObjectWidgetFactory(context,request,factory,template):
    widget=TemplateObjectWidget(context,request,factory,FileLocal(template,2))
    return widget


class TemplateSequenceWidget(ListSequenceWidget):
    def __init__(self, context, field, request, subwidget=None):
        super(TemplateSequenceWidget, self).__init__(context, field, request, 
subwidget)

        # This isn't really related to an ObjectView but provides a convinient
        # way of providing a template base Widget.
        class TemplateObjectWidgetView(ObjectWidgetView):
            template = ViewPageTemplateFile("mpgl1sequence.pt")

        self.view = TemplateObjectWidgetView(self, request)

    def __call__(self):
        return self.view()


def DictionaryWidgetFactory(field,request):
        
widget=zapi.getMultiAdapter((field,field.key_type,field.value_type,request),IInputWidget)
        return widget



class SimpleDictionaryWidget(BrowserWidget, InputWidget):
        """A widget for editing arbitrary dictionaries
        
                key_editsubwidget - optional edit subwidget for key components
                key_displaysubwidget - optional display subwidget for key 
components
                value_editsubwidget - optional edit subwidget for value 
components
        """
        implements(IInputWidget)
        _type= dict
        
        def 
__init__(self,context,key_type,value_type,request,key_editsubwidget=None,key_displaysubwidget=None,value_editsubwidget=None):
                super(SimpleDictionaryWidget,self).__init__(context,request)
                self.key_editsubwidget=key_editsubwidget
                self.key_displaysubwidget=key_displaysubwidget
                self.value_editsubwidget=value_editsubwidget
                self.context.key_type.bind(object())
                self.mayadd=True

        def _widgetpostproc(self,widget,key,keyorvalue):
                """For manipulating css classes of given elements"""

        def _sortkeys(self,keys):
                newkeys=[x for x in keys.__iter__()]
                newkeys.sort()
                return newkeys

        def _renderKeyAndCheckBox(self,render,key,i):
                render.append('<input class="editcheck" type="checkbox" 
name="%s.remove_%d" />' %(self.name,i))
                
keydisplaywidget=self._getWidget(str(i),IDisplayWidget,self.context.key_type,self.key_displaysubwidget,'key-display')
                self._widgetpostproc(keydisplaywidget,key,'key-display')
                keydisplaywidget.setRenderedValue(key)
                render.append(keydisplaywidget())

        def _renderitems(self,render):
                keys=self._data.keys()
                keys=self._sortkeys(keys)
                for i in range(len(keys)):
                        key=keys[i]
                        value=self._data[key]
                        render.append('<div>')
                        render.append('<span>')
                        self._renderKeyAndCheckBox(render,key,i)
                        
keyhiddenwidget=self._getWidget(str(i),IInputWidget,self.context.key_type,self.key_editsubwidget,'key')
                        keyhiddenwidget.setRenderedValue(key)
                        render.append(keyhiddenwidget.hidden())
                        render.append('</span>')
                        
valuewidget=self._getWidget(str(i),IInputWidget,self.context.value_type,self.value_editsubwidget,'value')
                        self._widgetpostproc(valuewidget,key,'value-edit')
                        valuewidget.setRenderedValue(value)
                        render.append('<span>' + valuewidget() + 
'</span></div>')
        
        def _renderbuttons(self,render):
                buttons = ''
                if ( len(self._data)>0 ) and len(self._data) > 
self.context.min_length:
                        button_label = _('remove-selected-items', "Remove 
selected items")
                        button_label = translate(button_label, 
context=self.request,default=button_label)
                        buttons += ('<input type="submit" value="%s" 
name="%s.remove"/>' % (button_label, self.name))
                if (self.context.max_length is None or len(self._data) < 
self.context.max_length) and self.mayadd:
                        field = self.context.value_type
                        button_label = _('Add %s')
                        button_label = translate(button_label, 
context=self.request, default=button_label)
                        button_label = button_label % (field.title or 
field.__name__)
                        buttons += '<input type="submit" name="%s.add" 
value="%s" />' % (self.name, button_label)
                        self._keypreproc()
                        
newkeywidget=self._getWidget('new',IInputWidget,self.context.key_type,self.key_editsubwidget,'key')
                        self._keypostproc()
                        self._widgetpostproc(newkeywidget,'','key-edit')
                        render.append('<div><span>%s</span></div>' 
%(newkeywidget(),) )
                if buttons:
                        render.append('<div><span>%s</span></div>' % buttons)
        
        def __call__(self):
                """Render the Widget"""
                assert self.context.key_type is not None
                assert self.context.value_type is not None
                render=[]
                render.append('<div><div id="%s">' % (self.name,))
                if not self._getRenderedValue():
                        if self.context.default is not None:
                                self._data=self.context.default
                        else:
                                self._data=self._type()
                
                self._renderitems(render)
                
                render.append('</div>')
                # possibly generate the "remove" and "add" buttons
                self._renderbuttons(render)
                
                render.append(self._getPresenceMarker(len(self._data)))
                render.append('</div>')
                text="\n".join(render)
                return text

        def _getWidget(self,i,interface,value_type,customwidget,mode):
                if customwidget is not None:
                        
widget=zapi.getMultiAdapter((value_type,self.request),interface,name=self.customwidget)
                else:
                        
widget=zapi.getMultiAdapter((value_type,self.request),interface)
                widget.setPrefix('%s.%s.%s.'%(self.name,i,mode))
                return widget

        def hidden(self):
                self._getRenderedValue()
                keys=self._data.keys()
                parts=[self._getPresenceMarker(len(self._data))]

                for i in range(len(keys)):
                        key=keys[i]
                        value=self._data[key]
                        
keywidget=self._getWidget(str(i),IInputWidget,self.context.key_type,self.key_displaysubwidget,'key')
                        keywidget.setRenderedValue(key)
                        
valuewidget=self._getWidget(str(i),IInputWidget,self.context.value_type,self.value_editsubwidget,'value')
                        parts.append(keywidget.hidden() + valuewidget.hidden())

                return "\n".join(parts)

        def _getPresenceMarker(self, count=0):
                return ('<input type="hidden" name="%s.count" value="%d" />'% 
(self.name, count))

        def _getRenderedValue(self):
                if not self._renderedValueSet():
                        if self.hasInput():
                                self._data=self._generateDict()
                        else:
                                self._data={}
                if self._data is None:
                        self._data=self._type()
                if len(self._data) < self.context.min_length:
                        """Don't know, what to do here :-("""
                return self._data

        def getInputValue(self):
                if self.hasInput():
                        dict=self._type(self._generateDict())
                        if dict != self.context.missing_value:
                                self.context.validate(dict)
                        elif self.context.required:
                                raise 
MissingInputError(self.context.__name__,self.context.title)
                        return dict
                raise MissingInputError(self.context.__name__, 
self.context.title)
        
        def applyChanges(self,content):
                field=self.context
                value=self.getInputValue()
                change=field.query(content,self) != value
                if change:
                        field.set(content,value)
                return change

        def hasInput(self):
                return (self.name+".count") in self.request.form

        def _generateDict(self):
                len_prefix=len(self.name)
                adding=False
                removing=[]
                if self.context.value_type is None:
                        return []

                try:
                        count=int(self.request.form[self.name+".count"])
                except ValueError:
                        raise WidgetInputError(self.context.__name__, 
self.context.title)

                keys={}
                values={}
                for i in range(count):
                        remove_key="%s.remove_%d" % (self.name,i)
                        if remove_key not in self.request.form:
                                
keywidget=self._getWidget(str(i),IInputWidget,self.context.key_type,self.key_displaysubwidget,'key')
                                
valuewidget=self._getWidget(str(i),IInputWidget,self.context.value_type,self.value_editsubwidget,'value')
                                keys[i]=keywidget.getInputValue()
                                values[i]=valuewidget.getInputValue()
                adding=(self.name+".add") in self.request.form
                
                mykeys=keys.items()
                mykeys.sort()
                dict={}
                for (i,key) in mykeys:
                        dict[key]=values[i]
                        
                if adding:
                        
newkeywidget=self._getWidget('new',IInputWidget,self.context.key_type,self.key_displaysubwidget,'key')
                        newkey=newkeywidget.getInputValue()
                        self.context.key_type.validate(newkey)
                        if dict.has_key(newkey):
                                raise InvalidValue
                        dict[newkey]=self.context.value_type.missing_value
                        
                return dict

        def _keypreproc(self):
                """Only for subclassing"""

        def _keypostproc(self):
                """Only for Subclassing"""

class FilterVocabulary(SimpleVocabulary):
        """Removes All terms from a vocabulary that are contained
                in a given dictionary. This is useful for filtering
                the vocabulary that is used to fill the new Choice-Widget
                of a Dict-Widget"""
        def __init__(self,vocabulary,dictionary):
                terms=[]
                self.empty=True
                for term in vocabulary._terms:
                        if not dictionary.has_key(term.value):
                                terms.append(term)
                                self.empty=False
                SimpleVocabulary.__init__(self,terms)

class ChoicyDictionaryWidget(SimpleDictionaryWidget):
        """This widget reduces available choices in a key_value-choice to only
                include values not already used"""
        def _keypreproc(self):
                """We use the keypreproc-hook to install a filter vocabulary
                        that removes all choices from the original vocabulary 
that
                        are already used."""
                if self.context.key_type.vocabulary is None:
                        return
                self.old_key_vocabulary=self.context.key_type.vocabulary
                
self.context.key_type.vocabulary=FilterVocabulary(self.context.key_type.vocabulary,self._data)
                if self.context.key_type.vocabulary.empty:
                        self.mayadd=False
                        

        def _keypostproc(self):
                """Reinstall the original dictionary"""
                if self.context.key_type.vocabulary is None:
                        return
                self.context.key_type.vocabulary=self.old_key_vocabulary

        def _renderbuttons(self,render):
                self._keypreproc()
                super(ChoicyDictionaryWidget,self)._renderbuttons(render)
                self._keypostproc()

class PathWidget(MpgTextWidget):
    """A Widget from zope.app.homefolder for entering absolute paths to 
objects"""
    def _toFieldValue(self, input):
        path = super(PathWidget, self)._toFieldValue(input)
        root = zapi.getRoot(self.context.context)
        try:
            proxy = zapi.traverse(root, path)
        except TraversalError, e:
            raise ConversionError(_('path is not correct !'), e)
        else:
            return removeSecurityProxy(proxy)

    def _toFormValue(self, value):
        if value is None:
            return ''
        return zapi.getPath(value)

class MpgSetInputWidget(ListSequenceWidget):
        _type=set
        def _generateSequence(self):
                """This is a modified method to provide functionality
                        for a +/- -controlled SetWidget"""
                if self.context.value_type is None:
                        return set([])
                try:
                        count = int(self.request.form[self.name + ".count"])
                except ValueError:
                        raise WidgetInputError(self.context.__name__, 
self.context.title)

                # pre-populate
                sequence=[]

                for i in range(count):
                        widget = self._getWidget(i)
                        if widget.hasValidInput():
                                # catch and set sequence widget errors to 
``_error`` attribute
                                try:
                                        sequence.append(widget.getInputValue())
                                except WidgetInputError, error:
                                        self._error = error
                                        raise self._error

                        remove_key = "%s.remove_%d" % (self.name, i)
                        add_key = "%s.add_%d" % (self.name, i)
                        if add_key in self.request.form:
                                
sequence.append(self.context.value_type.missing_value)
                        if remove_key in self.request.form:
                                del sequence[i]
                
                if (self.name + '.add') in self.request.form:
                        sequence.append(self.context.value_type.missing_value)
                return set(sequence)
        
        def _getRenderedValue(self):
                value=super(MpgSetInputWidget,self)._getRenderedValue()
                return set(value)
        
        def __call__(self):
                self._update()
                class TemplateObjectWidgetView(ObjectWidgetView):
                        template = ViewPageTemplateFile("mpgpml1set.pt")
                template = TemplateObjectWidgetView(self, self.request)
                
                return template()

class SetDisplayWidget(SequenceDisplayWidget):
        tag='ul'
        cssClass="setWidget"
        # TODO: missing-value-messages abgleichen


class MpgListInputWidget(ListSequenceWidget):
        def _generateSequence(self):
                """This is a modified method to provide functionality
                        for a +/- -controlled SequenceWidget"""
                if self.context.value_type is None:
                        return []
                try:
                        count = int(self.request.form[self.name + ".count"])
                except ValueError:
                        raise WidgetInputError(self.context.__name__, 
self.context.title)

                # pre-populate
                sequence = [None] * count
                found_up=None
                found_down=None
                found_remove=False
                found_add=None
                for i in reversed(range(count)):
                        widget = self._getWidget(i)
                        if widget.hasValidInput():
                                # catch and set sequence widget errors to 
``_error`` attribute
                                try:
                                        sequence[i] = widget.getInputValue()
                                except WidgetInputError, error:
                                        self._error = error
                                        raise self._error

                        remove_key = "%s.remove_%d" % (self.name, i)
                        add_key = "%s.add_%d" % (self.name, i)
                        up_key = "%s.up_%d" % (self.name,i)
                        down_key = "%s.down_%d" % (self.name,i)
                        if add_key in self.request.form:
                                found_add=i
                        if remove_key in self.request.form:
                                del sequence[i]
                                found_remove=True
                        if down_key in self.request.form:
                                found_down=i
                        if up_key in self.request.form:
                                found_up=i
                if not found_remove:
                        if found_up is not None:
                                temp=sequence[found_up-1]
                                sequence[found_up-1]=sequence[found_up]
                                sequence[found_up]=temp
                        if found_down is not None:
                                temp=sequence[found_down+1]
                                sequence[found_down+1]=sequence[found_down]
                                sequence[found_down+1]=temp
                        if found_add is not None:
                                
sequence[found_add:0]=[self.context.value_type.default]
                                        
                if (self.name + '.add') in self.request.form:
                        new=self.context.value_type.default
                        import pdb;pdb.set_trace()
                        if (new is None) and 
isinstance(self.context.value_type,Object):
                                
widget=zapi.getMultiAdapter((self.context.value_type,self.request),IInputWidget)
                                new=widget.factory()
                        sequence.append(new)
                        
                return sequence
        
        def __call__(self):
                self._update()
                self.listcontrollerclasses=''
                if isinstance(self.context.value_type ,Object):
                        self.listcontrollerclasses='Object'

                class TemplateObjectWidgetView(ObjectWidgetView):
                        template = ViewPageTemplateFile("mpgpml1sequence.pt")
                template = TemplateObjectWidgetView(self, self.request)
                return template()

        def _getRenderedValue(self):
                """Returns a sequence from the request or _data"""
                if self._renderedValueSet():
                        if self._data is None:
                                sequence=[]
                        else:
                                sequence = list(self._data)
                elif self.hasInput():
                        sequence = self._generateSequence()
                else:
                        sequence = []
                # ensure minimum number of items in the form
                while len(sequence) < self.context.min_length:
                        # Shouldn't this use 
self.field.value_type.missing_value,
                        # instead of None?
                        sequence.append(self.context.value_type.default)
                return sequence


def ObjectSequenceWidget(listfield,objectfield,request):
        """Dispatcher Widget that tries to find a specialized list widget for a
                given Object()-schema with fallback to 
Object()-default-widget"""
        
widget=zapi.queryMultiAdapter((listfield,objectfield.schema,request),IInputWidget)
        if widget is None:
                return MpgListInputWidget(listfield,objectfield,request)
        return widget

from zope.schema.interfaces import WrongContainedType, ValidationError
from zope.schema import Dict
from zope.schema._field import AbstractCollection

def _validate_dict(value_type,key_type, value, errors=None):
        if errors is None:
                errors=[]
        if value_type is None:
                return errors
        if key_type is None:
                return errors
        
        for (key,vl) in value.items():
                try:
                        key_type.validate(key)
                except ValidationError, error:
                        errors.append(error)
                try:
                        value_type.validate(vl)
                except ValidationError, error:
                        errors.append(error)
        return errors

class FixedDict(Dict,AbstractCollection):
        """We have to fix the bind-method of Dict"""
        def bind(self, object):
                clone=AbstractCollection.bind(self,object)
                if clone.key_type is not None:
                        clone.key_type = clone.key_type.bind(object)
                return clone

        def _validate(self,value):
                errors=_validate_dict(self.value_type,self.key_type,value)
                if len(errors) > 0:
                        raise WrongContainedType(errors)
_______________________________________________
Zope3-users mailing list
Zope3-users@zope.org
http://mail.zope.org/mailman/listinfo/zope3-users

Reply via email to