Skip to content

Commit

Permalink
Merge branch 'master' into multiple-text-field
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobwegner committed Nov 9, 2017
2 parents 31994d1 + ef6b1d4 commit f9b9766
Show file tree
Hide file tree
Showing 49 changed files with 1,165 additions and 214 deletions.
6 changes: 6 additions & 0 deletions .gitignore
@@ -1,3 +1,9 @@
docs/_build
formly.egg-info
dist
*.pyc
.coverage
.tox
.python-version
.eggs/

2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -6,8 +6,8 @@ python:
- "3.5"
env:
- DJANGO=1.8
- DJANGO=1.9
- DJANGO=1.10
- DJANGO=master
matrix:
exclude:
install:
Expand Down
5 changes: 4 additions & 1 deletion README.rst
Expand Up @@ -21,4 +21,7 @@ formly
A forms/survey generator for dynamically constructor multi-page surveys that have the ability to be non-linear.


development sponsored by [Midwest Communications](http://mwcradio.com/)
development sponsored by `Midwest Communications`_ and `Massachusetts General Hospital`_.

.. _Midwest Communications: http://mwcradio.com/
.. _Massachusetts General Hospital: http://www.massgeneral.org/
33 changes: 33 additions & 0 deletions docs/changelog.rst
Expand Up @@ -3,6 +3,39 @@
ChangeLog
=========

0.12.0
------

- fix broken migrations from 0.11.0

0.11.0
------

- add support for Rating field

0.10.2
------

- fix app to work with a custom user module
- add missing migration for formly.Field

0.10.1
------

- fix Field.form_field() bug when Likert field has no choices

0.10
-----

- add Likert-style field widget and presentation


0.9
---

- make label and help_text textfields


0.6
---

Expand Down
79 changes: 76 additions & 3 deletions docs/fields.rst
Expand Up @@ -35,7 +35,7 @@ radio choices
-------------

The ``radio choices`` field type is a ``django.forms.ChoiceField`` with a
``django.forms.RadioSelect`` widget, populated with choices as specified at
``django.forms.RadioSelect`` widget, populated with choices specified at
design time.


Expand All @@ -44,15 +44,15 @@ dropdown field

The ``dropdown field`` is a select field generated from a
``django.forms.ChoiceField`` with a ``django.forms.Select`` widget, populated
with choices as specified at design time.
with choices specified at design time.


checkbox field
--------------

The ``checkbox field`` is a field generated from a
``django.forms.MultipleChoiceField`` with a ``django.forms.CheckboxInput`` widget,
populated with choices as specified at design time. This field allows for
populated with choices specified at design time. This field allows for
multiple selections.


Expand All @@ -75,3 +75,76 @@ boolean field

The ``boolean field`` renders and processes input using
``django.forms.BooleanField``.


multiple text field
-------------------

The ``multiple text`` field type presents a number of single line fields.
The number of fields is specified at design time.


likert scale field
------------------

The ``likert scale`` field type is a ``django.forms.ChoiceField``,
populated with choices specified at design time. The field template
``formly/templates/bootstrapform/field.html`` emits:

<ul class="likert-question">
{{ field }}
</ul>

for hooking in CSS design. The following sample CSS presents a Likert field
in familiar horizontal layout. You should add this (or similar)
CSS to your project to get Likert-scale presentation.

form .likert-question {
list-style:none;
width:100%;
margin:0;
padding:0 0 35px;
display:block;
border-bottom:2px solid #efefef;
}
form .likert-question:last-of-type {
border-bottom:0;
}
form .likert-question:before {
content: '';
position:relative;
top:13px;
left:13%;
display:block;
background-color:#dfdfdf;
height:4px;
width:75%;
}
form .likert-question li {
display:inline-block;
width:19%;
text-align:center;
vertical-align: top;
}
form .likert-question li input[type=radio] {
display:block;
position:relative;
top:0;
left:50%;
margin-left:-6px;
}
form .likert-question li label {
width:100%;
}


rating scale field
------------------

The ``rating scale`` field type is a ``django.forms.ChoiceField``,
populated with choices specified at design time. The field template
``formly/templates/bootstrapform/field.html`` emits:

<ul class="rating-question">
{{ field }}
</ul>
9 changes: 9 additions & 0 deletions docs/templates.rst
Expand Up @@ -133,3 +133,12 @@ A template for displaying the results of a given survey.

This template is rendered for the end user to complete a particular survey, it is always
rendered with the appropriate page for the user.


``formly/bootstrapform/field.html``
------------------------

:Context: ``field``

This modified ``django-bootstrap-form`` template renders the various field types,
including special handling for Likert and Rating fields.
4 changes: 2 additions & 2 deletions formly/auth_backend.py
@@ -1,7 +1,7 @@
from pinax.apps.account import auth_backends
from django.contrib.auth.backends import ModelBackend


class AuthenticationBackend(auth_backends.AuthenticationBackend):
class AuthenticationBackend(ModelBackend):
"""
Permissions that do not receive an object:
Expand Down
25 changes: 25 additions & 0 deletions formly/fields.py
@@ -0,0 +1,25 @@
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _


class LimitedMultipleChoiceField(forms.MultipleChoiceField):
def __init__(self, *args, **kwargs):
self.maximum_choices = kwargs.pop("maximum_choices")

self.default_error_messages.update({
'maximum_choices': _('You may select at most %(maximum)d choices (%(selected)d selected)')
})

super(LimitedMultipleChoiceField, self).__init__(*args, **kwargs)

def validate(self, value):
super(LimitedMultipleChoiceField, self).validate(value)

selected_count = len(value)
if self.maximum_choices and selected_count > self.maximum_choices:
raise ValidationError(
self.error_messages['maximum_choices'],
code='maximum_choices',
params={'maximum': self.maximum_choices, 'selected': selected_count},
)
27 changes: 26 additions & 1 deletion formly/forms/design.py
@@ -1,6 +1,6 @@
from django import forms

from formly.models import Survey, Page, Field, FieldChoice
from formly.models import Survey, Page, Field, FieldChoice, OrdinalScale


class SurveyCreateForm(forms.ModelForm):
Expand Down Expand Up @@ -32,6 +32,31 @@ class Meta:
]


class OrdinalScaleForm(forms.ModelForm):

scale = forms.CharField(required=True)

def __init__(self, *args, **kwargs):
self.balanced = kwargs.pop("balanced", False)
super(OrdinalScaleForm, self).__init__(*args, **kwargs)

def clean_scale(self):
scale = self.cleaned_data["scale"]
if not scale:
raise forms.ValidationError("You must provide scale values, delimited by commas.")
scale_choices = [s.strip() for s in scale.split(",")]
if self.balanced and len(scale_choices) % 2 != 1:
raise forms.ValidationError("A Likert scale must have an odd number of choices.")
return scale_choices

class Meta:
model = OrdinalScale
fields = [
"name",
"scale"
]


class FieldForm(forms.ModelForm):

class Meta:
Expand Down
13 changes: 13 additions & 0 deletions formly/forms/widgets.py
@@ -0,0 +1,13 @@
from django.forms.widgets import RadioSelect


class LikertSelect(RadioSelect):
"""
This class differentiates Likert-scale radio selects
from "normal" radio selects for presentation purposes.
"""
pass


class RatingSelect(RadioSelect):
pass
45 changes: 45 additions & 0 deletions formly/migrations/0004_auto_20161206_1401.py
@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-06 14:01
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('formly', '0003_auto_20160816_2022'),
]

operations = [
migrations.CreateModel(
name='LikertChoice',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('label', models.CharField(max_length=100)),
('score', models.IntegerField()),
],
),
migrations.CreateModel(
name='LikertScale',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
],
),
migrations.AlterField(
model_name='field',
name='field_type',
field=models.IntegerField(choices=[(0, 'Free Response - One Line'), (1, 'Free Response - Box'), (2, 'Multiple Choice - Pick One'), (4, 'Multiple Choice - Pick One (Dropdown)'), (5, 'Multiple Choice - Can select multiple answers'), (3, 'Date'), (6, 'File Upload'), (7, 'True/False'), (8, 'Multiple Free Responses - Single Lines'), (9, 'Likert Scale')]),
),
migrations.AddField(
model_name='likertchoice',
name='scale',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='formly.LikertScale'),
),
migrations.AlterUniqueTogether(
name='likertchoice',
unique_together=set([('scale', 'label'), ('scale', 'score')]),
),
]
21 changes: 21 additions & 0 deletions formly/migrations/0005_field_scale.py
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-06 14:08
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('formly', '0004_auto_20161206_1401'),
]

operations = [
migrations.AddField(
model_name='field',
name='scale',
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='formly.LikertScale'),
),
]
21 changes: 21 additions & 0 deletions formly/migrations/0006_auto_20161206_1415.py
@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.10.4 on 2016-12-06 14:15
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('formly', '0005_field_scale'),
]

operations = [
migrations.AlterField(
model_name='field',
name='scale',
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='fields', to='formly.LikertScale'),
),
]
25 changes: 25 additions & 0 deletions formly/migrations/0007_help_text_and_label_to_textfield.py
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-05-11 16:06
from __future__ import unicode_literals

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('formly', '0006_auto_20161206_1415'),
]

operations = [
migrations.AlterField(
model_name='field',
name='help_text',
field=models.TextField(blank=True),
),
migrations.AlterField(
model_name='field',
name='label',
field=models.TextField(),
),
]

0 comments on commit f9b9766

Please sign in to comment.