Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

handle flex fields and fix some other migration-related issues #367

Open
wants to merge 14 commits into
base: 1.x
Choose a base branch
from
1 change: 1 addition & 0 deletions collective/easyform/browser/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<include file="fields.zcml" />
<include file="actions.zcml" />
<include file="widgets.zcml" />
<include file="likert.zcml" />
<browser:page
for="collective.easyform.interfaces.IEasyForm"
name="view"
Expand Down
65 changes: 65 additions & 0 deletions collective/easyform/browser/likert.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
__docformat__ = "reStructuredText"
import zope.component
import zope.interface
import zope.schema.interfaces
from zope.pagetemplate.interfaces import IPageTemplate
from plone.memoize.view import memoize

from z3c.form import interfaces
from z3c.form.widget import Widget, FieldWidget
from z3c.form.browser import widget

from collective.easyform.interfaces import ILikertWidget

@zope.interface.implementer_only(ILikertWidget)
class LikertWidget(widget.HTMLTextInputWidget, Widget):
"""Input type text widget implementation."""

klass = u'likert-widget'
css = u'text'
value = u''

def update(self):
super(LikertWidget, self).update()
widget.addFieldClass(self)

def extract(self, default=interfaces.NO_VALUE):
"""See z3c.form.interfaces.IWidget."""
answers = []
if self.field.questions is None:
return ''
for index, question in enumerate(self.field.questions):
question_answer = self.extract_question_answer(index, default)
if question_answer is not None:
answers.append(question_answer)
return u', '.join(answers)

def extract_question_answer(self, index, default):
"""See z3c.form.interfaces.IWidget."""
name = '%s.%i' % (self.name, index)
if (name not in self.request and
'%s-empty-marker' % name in self.request):
return None
value = self.request.get(name, default)
if value != default:
return '%i: %s' % (index + 1, value)
else:
return None

@memoize
def parsed_values(self):
return self.field.parse(self.value)

def checked(self, question_number, answer_number):
values = self.parsed_values()
if not values or (question_number) not in values:
return False
else:
return values[question_number] == self.field.answers[answer_number - 1]


@zope.component.adapter(zope.schema.interfaces.IField, interfaces.IFormLayer)
@zope.interface.implementer(interfaces.IFieldWidget)
def LikertFieldWidget(field, request):
"""IFieldWidget factory for TextWidget."""
return FieldWidget(field, LikertWidget(request))
40 changes: 40 additions & 0 deletions collective/easyform/browser/likert.zcml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:z3c="http://namespaces.zope.org/z3c"
i18n_domain="z3c.form">

<class class=".likert.LikertWidget">
<require
permission="zope.Public"
interface="..interfaces.ILikertWidget"
/>
</class>

<adapter
factory=".likert.LikertFieldWidget"
for="..interfaces.ILikert
z3c.form.interfaces.IFormLayer"
/>

<z3c:widgetTemplate
mode="display"
widget="..interfaces.ILikertWidget"
layer="z3c.form.interfaces.IFormLayer"
template="likert_display.pt"
/>

<z3c:widgetTemplate
mode="input"
widget="..interfaces.ILikertWidget"
layer="z3c.form.interfaces.IFormLayer"
template="likert_input.pt"
/>

<z3c:widgetTemplate
mode="hidden"
widget="..interfaces.ILikertWidget"
layer="z3c.form.interfaces.IFormLayer"
template="likert_hidden.pt"
/>

</configure>
22 changes: 22 additions & 0 deletions collective/easyform/browser/likert_display.pt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal"
tal:omit-tag="">
<span id="" class=""
tal:attributes="id view/id;
class view/klass;
style view/style;
title view/title;
lang view/lang;
onclick view/onclick;
ondblclick view/ondblclick;
onmousedown view/onmousedown;
onmouseup view/onmouseup;
onmouseover view/onmouseover;
onmousemove view/onmousemove;
onmouseout view/onmouseout;
onkeypress view/onkeypress;
onkeydown view/onkeydown;
onkeyup view/onkeyup"><tal:block
condition="view/value" content="view/value"
/></span>
</html>
13 changes: 13 additions & 0 deletions collective/easyform/browser/likert_hidden.pt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<div xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal"
tal:omit-tag="">
<input id="" name="" value="" class="hidden-widget" title=""
tabindex="" accesskey=""
type="hidden"
tal:attributes="id view/id;
name view/name;
title view/title;
tabindex view/tabindex;
accesskey view/accesskey;
value view/value" />
</div>
29 changes: 29 additions & 0 deletions collective/easyform/browser/likert_input.pt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:tal="http://xml.zope.org/namespaces/tal"
tal:omit-tag="">
<table class="likert listing" tal:attributes="class string:${view/klass} listing;">
<tbody><tr>
<th>&nbsp;</th>
<th tal:repeat="answer view/field/answers" tal:content="answer" />
</tr>
<tal:block tal:repeat="question view/field/questions">
<tr tal:define="q_even repeat/question/even"
tal:attributes="class python:'even' if q_even else 'odd'">
<td>
<span tal:content="question">Question Number One</span>
<input name="field-empty-marker" type="hidden" value="1"
tal:attributes="name string:${view/name}-${repeat/question/index}-empty-marker" />
<td tal:repeat="answer view/field/answers"
onclick="jQuery(event.target).children('input').click()">
<input type="radio"
tal:define="q_number repeat/question/number;
a_number repeat/answer/number;"
tal:attributes="id string:${view/name}.${repeat/question/index}_${repeat/answer/index};
name string:${view/name}.${repeat/question/index};
value answer;
checked python:view.checked(q_number, a_number);" />
</td>
</tr>
</tal:block>
</tbody></table>
</html>
40 changes: 40 additions & 0 deletions collective/easyform/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from collective.easyform.interfaces import IFieldExtender
from collective.easyform.interfaces import ILabel
from collective.easyform.interfaces import IReCaptcha
from collective.easyform.interfaces import ILikert
from collective.easyform.interfaces import IRichLabel
from collective.easyform.validators import IFieldValidator
from plone.schemaeditor.fields import FieldFactory
Expand Down Expand Up @@ -140,3 +141,42 @@ class ReCaptcha(TextLine):
ReCaptchaFactory = FieldFactory(
ReCaptcha, _(u'label_recaptcha_field', default=u'ReCaptcha'))
ReCaptchaHandler = BaseHandler(ReCaptcha)

@implementer(ILikert)
class Likert(TextLine):
"""A Likert field"""

def __init__(self, **kwargs):
self.answers = kwargs.get('answers', None)
if 'answers' in kwargs:
del kwargs['answers']
self.questions = kwargs.get('questions', None)
if 'questions' in kwargs:
del kwargs['questions']
Field.__init__(self, **kwargs)

def _validate(self, value):
super(Likert, self)._validate(value)
self.parse(value)

def parse(self, value):
result = dict()
lines = value.split(',')
for line in lines:
if not line:
continue
id, answer = line.split(':')
answer = answer.strip()
if answer not in self.answers:
raise ValueError('Invalid answer value.')
index = int(id)
if index < 1 or index > len(self.questions):
raise ValueError('Invalid question index.')
result[index] = answer
return result


LikertFactory = FieldFactory(
Likert, _(u"label_likert_field", default=u"Likert")
)
LikertHandler = BaseHandler(Likert)
8 changes: 8 additions & 0 deletions collective/easyform/fields.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,12 @@
factory="plone.formwidget.recaptcha.ReCaptchaValidator"
/>
</configure>
<utility
name="collective.easyform.fields.Likert"
component=".fields.LikertFactory"
/>
<utility
name="collective.easyform.fields.Likert"
component=".fields.LikertHandler"
/>
</configure>
27 changes: 27 additions & 0 deletions collective/easyform/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from plone.schemaeditor.interfaces import IFieldContext
from plone.schemaeditor.interfaces import IFieldEditorExtender
from plone.schemaeditor.interfaces import ISchemaContext
from plone.schemaeditor.schema import ITextLinesField
from plone.supermodel.model import Schema
from plone.supermodel.model import fieldset
from plone.z3cform.interfaces import IFormWrapper
Expand All @@ -53,6 +54,10 @@
from zope.schema.interfaces import ITextLine
from zope.tales.tales import CompilerError

import zope.interface
import zope.schema.interfaces
import z3c.form.interfaces

try:
from plone.schemaeditor import SchemaEditorMessageFactory as __
except ImportError:
Expand Down Expand Up @@ -1087,3 +1092,25 @@ class IReCaptcha(ITextLine):
class IFieldValidator(Interface):

"""Base marker for field validators"""


class ILikert(zope.schema.interfaces.IField):

questions = zope.schema.List(
title=_(u'Possible questions'),
description=_(u'Enter allowed choices one per line.'),
required=zope.schema.interfaces.IChoice['vocabulary'].required,
default=zope.schema.interfaces.IChoice['vocabulary'].default,
value_type=zope.schema.TextLine())
zope.interface.alsoProvides(questions, ITextLinesField)

answers = zope.schema.List(
title=_(u'Possible answers'),
description=_(u'Enter allowed choices one per line.'),
required=zope.schema.interfaces.IChoice['vocabulary'].required,
default=zope.schema.interfaces.IChoice['vocabulary'].default,
value_type=zope.schema.TextLine())
zope.interface.alsoProvides(questions, ITextLinesField)

class ILikertWidget(z3c.form.interfaces.IWidget):
"""Likert widget."""
Loading