diff --git a/django/forms/models.py b/django/forms/models.py index 3a288203d3..607ac455fe 100644 --- a/django/forms/models.py +++ b/django/forms/models.py @@ -9,7 +9,8 @@ from django.utils.datastructures import SortedDict from django.utils.text import get_text_list, capfirst from django.utils.translation import ugettext_lazy as _, ugettext -from django.core.exceptions import ValidationError, NON_FIELD_ERRORS +from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, \ + FieldError from django.core.validators import EMPTY_VALUES from util import ErrorList from forms import BaseForm, get_declared_fields @@ -224,6 +225,15 @@ class ModelFormMetaclass(type): # If a model is defined, extract form fields from it. fields = fields_for_model(opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback) + # make sure opts.fields doesn't specify an invalid field + none_model_fields = [k for k, v in fields.iteritems() if not v] + missing_fields = set(none_model_fields) - \ + set(declared_fields.keys()) + if missing_fields: + message = 'Unknown field(s) (%s) specified for %s' + message = message % (', '.join(missing_fields), + opts.model.__name__) + raise FieldError(message) # Override default model fields with any custom declared ones # (plus, include all the other declared fields). fields.update(declared_fields) diff --git a/tests/regressiontests/model_forms_regress/tests.py b/tests/regressiontests/model_forms_regress/tests.py index 569be2a231..baf769c02a 100644 --- a/tests/regressiontests/model_forms_regress/tests.py +++ b/tests/regressiontests/model_forms_regress/tests.py @@ -5,6 +5,7 @@ from django import forms from django.forms.models import modelform_factory, ModelChoiceField from django.conf import settings from django.test import TestCase +from django.core.exceptions import FieldError from models import Person, RealPerson, Triple, FilePathModel, Article, \ Publication, CustomFF, Author, Author1, Homepage @@ -294,3 +295,41 @@ class FormFieldCallbackTests(TestCase): self.assertRaises(TypeError, modelform_factory, Person, formfield_callback='not a function or callable') + +class InvalidFieldAndFactory(TestCase): + """ Tests for #11905 """ + + def test_extra_field_model_form(self): + try: + class ExtraPersonForm(forms.ModelForm): + """ ModelForm with an extra field """ + + age = forms.IntegerField() + + class Meta: + model = Person + fields = ('name', 'no-field') + except FieldError, e: + # Make sure the exception contains some reference to the + # field responsible for the problem. + self.assertTrue('no-field' in e.args[0]) + else: + self.fail('Invalid "no-field" field not caught') + + def test_extra_declared_field_model_form(self): + try: + class ExtraPersonForm(forms.ModelForm): + """ ModelForm with an extra field """ + + age = forms.IntegerField() + + class Meta: + model = Person + fields = ('name', 'age') + except FieldError: + self.fail('Declarative field raised FieldError incorrectly') + + def test_extra_field_modelform_factory(self): + self.assertRaises(FieldError, modelform_factory, + Person, fields=['no-field', 'name']) +