Skip to content

Commit

Permalink
Merge 0c609f1 into bef134b
Browse files Browse the repository at this point in the history
  • Loading branch information
julianandrews committed Oct 2, 2015
2 parents bef134b + 0c609f1 commit eea21c1
Show file tree
Hide file tree
Showing 19 changed files with 150 additions and 121 deletions.
2 changes: 2 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[run]
omit = *tests*
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ tests/.coverage
tests/htmlcov/
dist/
.tox/
.cache/
.coverage
31 changes: 16 additions & 15 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
language: python
python:
- "2.7"
- "3.3"
- "3.4"
cache: pip
sudo: false
env:
- DJANGO_PACKAGE=https://github.com/django/django/archive/stable/1.5.x.zip
- DJANGO_PACKAGE=https://github.com/django/django/archive/stable/1.6.x.zip
- DJANGO_PACKAGE=https://github.com/django/django/archive/stable/1.7.x.zip
matrix:
include:
- python: "2.6"
env: DJANGO_PACKAGE=https://github.com/django/django/archive/stable/1.4.x.zip
- python: "2.7"
env: DJANGO_PACKAGE=https://github.com/django/django/archive/stable/1.4.x.zip
matrix:
- TOX_ENV=py27-dj17
- TOX_ENV=py27-dj18
- TOX_ENV=py34-dj17
- TOX_ENV=py34-dj18
install:
- pip install -q $DJANGO_PACKAGE --use-mirrors
- python setup.py install
- pip install mock
- pip install --upgrade pip
- pip install tox
- pip install coveralls
script: make coverage
after_success: cd tests && coveralls
script:
- tox -e $TOX_ENV coverage
after_success:
- coveralls
after_script:
- cat .tox/$TOX_ENV/log/*.log
8 changes: 3 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
TESTS=tests betterforms
SETTINGS=tests.sqlite_test_settings
COVERAGE_COMMAND=
COVERAGE_ARGS=


test: test-builtin

test-builtin:
cd tests && DJANGO_SETTINGS_MODULE=$(SETTINGS) $(COVERAGE_COMMAND) ./manage.py test --traceback $(TESTS) --verbosity=2
DJANGO_SETTINGS_MODULE=$(SETTINGS) py.test $(COVERAGE_ARGS)

coverage:
+make test COVERAGE_COMMAND='coverage run --source=betterforms --omit="*tests*" --branch --parallel-mode'
cd tests && coverage combine && coverage html
+make test COVERAGE_ARGS='--cov-config .coveragerc --cov-report html --cov-report= --cov=betterforms'

docs:
cd docs && $(MAKE) html
Expand Down
31 changes: 26 additions & 5 deletions betterforms/multiform.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
except ImportError: # Django < 1.7
from django.forms.util import ErrorDict, ErrorList # NOQA

from django.core.exceptions import ValidationError
from django.utils.encoding import python_2_unicode_compatible
from django.utils.safestring import mark_safe
from django.utils.six.moves import reduce
Expand Down Expand Up @@ -38,6 +39,7 @@ def __init__(self, data=None, files=None, *args, **kwargs):
if self.initials is None:
self.initials = {}
self.forms = OrderedDict()
self.crossform_errors = []

for key, form_class in self.form_classes.items():
fargs, fkwargs = self.get_form_args_kwargs(key, args, kwargs)
Expand Down Expand Up @@ -73,13 +75,32 @@ def __iter__(self):
def is_bound(self):
return any(form.is_bound for form in self.forms.values())

def clean(self):
"""
Raises any ValidationErrors required for cross form validation. Should
return a dict of cleaned_data objects for any forms whose data should
be overridden.
"""
return self.cleaned_data

def add_crossform_error(self, e):
self.crossform_errors.append(e)

def is_valid(self):
return all(form.is_valid() for form in self.forms.values())
forms_valid = all(form.is_valid() for form in self.forms.values())
try:
cleaned_data = self.clean()
except ValidationError as e:
self.add_crossform_error(e)
else:
if cleaned_data is not None:
for key, data in cleaned_data.items():
self.forms[key].cleaned_data = data
return forms_valid and not self.crossform_errors

def non_field_errors(self):
return ErrorList(chain.from_iterable(
form.non_field_errors() for form in self.forms.values()
))
form_errors = (form.non_field_errors() for form in self.forms.values())
return ErrorList(chain(self.crossform_errors, *form_errors))

def as_table(self):
return mark_safe(''.join(form.as_table() for form in self.forms.values()))
Expand Down Expand Up @@ -109,7 +130,7 @@ def visible_fields(self):
def cleaned_data(self):
return OrderedDict(
(key, form.cleaned_data)
for key, form in self.forms.items()
for key, form in self.forms.items() if form.is_valid()
)


Expand Down
47 changes: 27 additions & 20 deletions betterforms/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import mock

import django
from django import forms
from django.core.exceptions import ImproperlyConfigured
from django.conf import settings
Expand Down Expand Up @@ -332,6 +333,7 @@ class Meta:
)
self.TestForm = TestForm

@unittest.skipIf(django.VERSION < (1, 8), 'HTML output is slightly different in 1.8')
def test_non_fieldset_form_rendering(self):
class TestForm(BetterForm):
# Set the label_suffix to an empty string for consistent results
Expand All @@ -353,15 +355,15 @@ class TestForm(BetterForm):
render_to_string('betterforms/form_as_fieldsets.html', env),
"""
<div class="required a formField">
<label for="id_a">A</label>
<label class="required" for="id_a">A</label>
<input id="id_a" name="a" type="text" />
</div>
<div class="b formField">
<label for="id_b">B</label>
<input id="id_b" name="b" type="text" />
</div>
<div class="required c formField">
<label for="id_c">C</label>
<label class="required" for="id_c">C</label>
<input id="id_c" name="c" type="text" />
</div>
""",
Expand All @@ -371,7 +373,7 @@ class TestForm(BetterForm):
render_to_string('betterforms/form_as_fieldsets.html', env),
"""
<div class="required error a formField">
<label for="id_a">A</label>
<label class="required" for="id_a">A</label>
<input id="id_a" name="a" type="text" />
<ul class="errorlist"><li>this is an error message</li></ul>
</div>
Expand All @@ -380,12 +382,13 @@ class TestForm(BetterForm):
<input id="id_b" name="b" type="text" />
</div>
<div class="required c formField">
<label for="id_c">C</label>
<label class="required" for="id_c">C</label>
<input id="id_c" name="c" type="text" />
</div>
""",
)

@unittest.skipIf(django.VERSION < (1, 8), 'HTML output is slightly different in 1.8')
def test_include_tag_rendering(self):
form = self.TestForm()
env = {
Expand All @@ -399,17 +402,17 @@ def test_include_tag_rendering(self):
"""
<fieldset class="formFieldset first">
<div class="required a formField">
<label for="id_a">A</label>
<label class="required" for="id_a">A</label>
<input id="id_a" name="a" type="text" />
</div>
<div class="required b formField">
<label for="id_b">B</label>
<label class="required" for="id_b">B</label>
<input id="id_b" name="b" type="text" />
</div>
</fieldset>
<fieldset class="formFieldset second">
<div class="required c formField">
<label for="id_c">C</label>
<label class="required" for="id_c">C</label>
<input id="id_c" name="c" type="text" />
</div>
</fieldset>
Expand All @@ -421,24 +424,25 @@ def test_include_tag_rendering(self):
"""
<fieldset class="formFieldset first">
<div class="required error a formField">
<label for="id_a">A</label>
<label class="required" for="id_a">A</label>
<input id="id_a" name="a" type="text" />
<ul class="errorlist"><li>this is an error message</li></ul>
</div>
<div class="required b formField">
<label for="id_b">B</label>
<label class="required" for="id_b">B</label>
<input id="id_b" name="b" type="text" />
</div>
</fieldset>
<fieldset class="formFieldset second">
<div class="required c formField">
<label for="id_c">C</label>
<label class="required" for="id_c">C</label>
<input id="id_c" name="c" type="text" />
</div>
</fieldset>
""",
)

@unittest.skipIf(django.VERSION < (1, 8), 'HTML output is slightly different in 1.8')
def test_fields_django_form_required(self):
class TestForm(forms.Form):
a = forms.CharField(label='A:')
Expand Down Expand Up @@ -481,24 +485,25 @@ def test_form_as_ul(self):
form = self.TestForm()
form.as_ul()

@unittest.skipIf(django.VERSION < (1, 8), 'HTML output is slightly different in 1.8')
def test_form_as_p(self):
form = self.TestForm()
self.assertHTMLEqual(
form.as_p(),
"""
<fieldset class="formFieldset first">
<p class="required">
<label for="id_a">A</label>
<label class="required" for="id_a">A</label>
<input id="id_a" name="a" type="text" />
</p>
<p class="required">
<label for="id_b">B</label>
<label class="required" for="id_b">B</label>
<input id="id_b" name="b" type="text" />
</p>
</fieldset>
<fieldset class="formFieldset second">
<p class="required">
<label for="id_c">C</label>
<label class="required" for="id_c">C</label>
<input id="id_c" name="c" type="text" />
</p>
</fieldset>
Expand All @@ -512,23 +517,24 @@ def test_form_as_p(self):
<fieldset class="formFieldset first">
<p class="required error">
<ul class="errorlist"><li>this is an error</li></ul>
<label for="id_a">A</label>
<label class="required" for="id_a">A</label>
<input id="id_a" name="a" type="text" />
</p>
<p class="required">
<label for="id_b">B</label>
<label class="required" for="id_b">B</label>
<input id="id_b" name="b" type="text" />
</p>
</fieldset>
<fieldset class="formFieldset second">
<p class="required">
<label for="id_c">C</label>
<label class="required" for="id_c">C</label>
<input id="id_c" name="c" type="text" />
</p>
</fieldset>
""",
)

@unittest.skipIf(django.VERSION < (1, 8), 'HTML output is slightly different in 1.8')
def test_fieldset_legend(self):
class TestForm(BetterForm):
a = forms.CharField()
Expand All @@ -550,24 +556,25 @@ class Meta:
<fieldset class="formFieldset first">
<legend>First Fieldset</legend>
<p class="required">
<label for="id_a">A</label>
<label class="required" for="id_a">A</label>
<input id="id_a" name="a" type="text" />
</p>
<p class="required">
<label for="id_b">B</label>
<label class="required" for="id_b">B</label>
<input id="id_b" name="b" type="text" />
</p>
</fieldset>
<fieldset class="formFieldset second">
<legend>Second Fieldset</legend>
<p class="required">
<label for="id_c">C</label>
<label class="required" for="id_c">C</label>
<input id="id_c" name="c" type="text" />
</p>
</fieldset>
""",
)

@unittest.skipIf(django.VERSION < (1, 8), 'HTML output is slightly different in 1.8')
def test_css_classes_when_form_has_prefix(self):
class TestForm(BetterForm):
name = forms.CharField()
Expand All @@ -579,7 +586,7 @@ class TestForm(BetterForm):
render_to_string('betterforms/form_as_fieldsets.html', env),
"""
<div class="required prefix-name name formField">
<label for="id_prefix-name">Name</label>
<label class="required" for="id_prefix-name">Name</label>
<input type="text" id="id_prefix-name" name="prefix-name" />
</div>
"""
Expand Down
File renamed without changes.
2 changes: 2 additions & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
python_files = test_*.py tests.py
File renamed without changes.
14 changes: 14 additions & 0 deletions tests/tests/forms.py → tests/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,17 @@ class Step1Form(MultiModelForm):

class Step2Form(forms.Form):
confirm = forms.BooleanField(required=True)


class RaisesErrorCustomCleanMultiform(UserProfileMultiForm):
def clean(self):
cleaned_data = super(UserProfileMultiForm, self).clean()
raise ValidationError('It broke')
return cleaned_data


class ModifiesDataCustomCleanMultiform(UserProfileMultiForm):
def clean(self):
cleaned_data = super(UserProfileMultiForm, self).clean()
cleaned_data['profile']['display_name'] = "cleaned name"
return cleaned_data
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 comments on commit eea21c1

Please sign in to comment.