Ticket #17924: 17924.diff

File 17924.diff, 13.2 KB (added by Simon Meers, 12 years ago)
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    index 4d56f1d..0686525 100644
    a b and database field objects.  
    55
    66from __future__ import absolute_import, unicode_literals
    77
    8 from django.core.exceptions import ValidationError, NON_FIELD_ERRORS, FieldError
     8from django.core.exceptions import (
     9    ValidationError, NON_FIELD_ERRORS, FieldError, ImproperlyConfigured)
    910from django.core.validators import EMPTY_VALUES
    1011from django.forms.fields import Field, ChoiceField
    1112from django.forms.forms import BaseForm, get_declared_fields
    def model_to_dict(instance, fields=None, exclude=None):  
    131132            data[f.name] = f.value_from_object(instance)
    132133    return data
    133134
    134 def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None):
     135def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None, overrides=None):
    135136    """
    136137    Returns a ``SortedDict`` containing form fields for the given model.
    137138
    def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c  
    144145    """
    145146    field_list = []
    146147    ignored = []
     148    applied_overrides = []
    147149    opts = model._meta
    148150    for f in sorted(opts.fields + opts.many_to_many):
    149151        if not f.editable:
    def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c  
    157159        else:
    158160            kwargs = {}
    159161
     162        if overrides:
     163            for key, kwargs_overrides in overrides.iteritems():
     164                if (key == f.name or
     165                    (isinstance(key, type) and isinstance(f, key)) or
     166                    (callable(key) and key(f))):
     167                    kwargs.update(kwargs_overrides)
     168                    applied_overrides.append(key)
     169
    160170        if formfield_callback is None:
    161171            formfield = f.formfield(**kwargs)
    162172        elif not callable(formfield_callback):
    def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_c  
    168178            field_list.append((f.name, formfield))
    169179        else:
    170180            ignored.append(f.name)
     181
     182    if overrides:
     183        for key in overrides.keys():
     184            if not key in applied_overrides:
     185                if isinstance(key, (type, basestring)):
     186                    # TBC: should these pass silently?
     187                    raise ImproperlyConfigured(
     188                        'ModelForm.Meta.overrides contained unused key: %r' %
     189                        key)
     190                else:
     191                    pass # TBC: ignore callables either way?
     192
    171193    field_dict = SortedDict(field_list)
    172194    if fields:
    173195        field_dict = SortedDict(
    class ModelFormOptions(object):  
    182204        self.fields = getattr(options, 'fields', None)
    183205        self.exclude = getattr(options, 'exclude', None)
    184206        self.widgets = getattr(options, 'widgets', None)
     207        self.overrides = getattr(options, 'overrides', None)
    185208
    186209
    187210class ModelFormMetaclass(type):
    class ModelFormMetaclass(type):  
    203226        opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
    204227        if opts.model:
    205228            # If a model is defined, extract form fields from it.
    206             fields = fields_for_model(opts.model, opts.fields,
    207                                       opts.exclude, opts.widgets, formfield_callback)
     229            fields = fields_for_model(
     230                opts.model, opts.fields, opts.exclude, opts.widgets,
     231                formfield_callback, overrides=opts.overrides)
    208232            # make sure opts.fields doesn't specify an invalid field
    209233            none_model_fields = [k for k, v in fields.iteritems() if not v]
    210234            missing_fields = set(none_model_fields) - \
  • docs/topics/forms/modelforms.txt

    diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
    index 0ca3774..85c3e3a 100644
    a b widget::  
    388388The ``widgets`` dictionary accepts either widget instances (e.g.,
    389389``Textarea(...)``) or classes (e.g., ``Textarea``).
    390390
     391.. versionadded:: 1.5
     392
    391393If you want to further customize a field -- including its type, label, etc. --
    392 you can do this by declaratively specifying fields like you would in a regular
    393 ``Form``. Declared fields will override the default ones generated by using the
    394 ``model`` attribute.
     394you can use the ``overrides`` attribute of the inner ``Meta`` class. Using
     395this you can not only change the widget [#]_ but also override any other
     396keyword argument that the form field's ``__init__`` accepts, such as
     397``label``, ``help_text``, ``widget``, ``required``, ``form_class``, etc. The
     398``overrides`` attribute is a dictionary with keys which can be either:
     399
     400* a field name such as ``'name'``, ``'birth_date'`` to override a specific
     401  field, or
     402* a model field class as ``models.CharField``, ``models.FloatField`` to
     403  override all fields of a particular type, or
     404* a callable to perform more complex matching, such as
     405  ``lambda f: f.name.startswith('question_')``
     406
     407The values of the ``overrides`` dictionary are simply dictionaries of keyword
     408arguments that are passed to :meth:`~django.forms.Field.__init__` (or subclass thereof).
     409
     410.. [#] FIXME: "There should be one-- and preferably only one --obvious way to do it."
     411
     412You can also completely override form fields by declaratively specifying fields like
     413you would in a regular ``Form``. Declared fields will override the default ones
     414generated by using the ``model`` attribute.
    395415
    396416For example, if you wanted to use ``MyDateFormField`` for the ``pub_date``
    397417field, you could do the following::
  • tests/regressiontests/forms/models.py

    diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py
    index 18e6ddc..ffc3d32 100644
    a b class Group(models.Model):  
    7474
    7575
    7676class Cheese(models.Model):
    77    name = models.CharField(max_length=100)
     77    name = models.CharField(max_length=100)
     78
     79
     80class WalkCategory(models.Model):
     81    name = models.CharField(max_length=100)
     82
     83    def __unicode__(self):
     84        return self.name
     85
     86
     87class Walk(models.Model):
     88    name = models.CharField(max_length=255)
     89    silliness = models.FloatField(
     90        help_text='0 = not silly at all, 1 = completely silly')
     91    description = models.CharField(max_length=1024, blank=True)
     92    difficulty = models.PositiveSmallIntegerField(
     93        choices=tuple(
     94            enumerate(['Easy', 'Moderate', 'Difficult', 'Impossible'])
     95        )
     96    )
     97    category = models.ForeignKey(WalkCategory)
     98
     99    def __unicode__(self):
     100        return self.name
  • tests/regressiontests/forms/tests/__init__.py

    diff --git a/tests/regressiontests/forms/tests/__init__.py b/tests/regressiontests/forms/tests/__init__.py
    index 8e2150c..1784cb1 100644
    a b from .input_formats import (LocalizedTimeTests, CustomTimeInputFormatsTests,  
    1313    CustomDateTimeInputFormatsTests, SimpleDateTimeFormatTests)
    1414from .media import FormsMediaTestCase, StaticFormsMediaTestCase
    1515from .models import (TestTicket12510, ModelFormCallableModelDefault,
    16     FormsModelTestCase, RelatedModelFormTests)
     16    FormsModelTestCase, RelatedModelFormTests, OverridesTest)
    1717from .regressions import FormsRegressionsTestCase
    1818from .util import FormsUtilTestCase
    1919from .validators import TestFieldWithValidators
  • tests/regressiontests/forms/tests/models.py

    diff --git a/tests/regressiontests/forms/tests/models.py b/tests/regressiontests/forms/tests/models.py
    index 7687335..204b125 100644
    a b import datetime  
    55
    66from django.core.files.uploadedfile import SimpleUploadedFile
    77from django.db import models
    8 from django.forms import Form, ModelForm, FileField, ModelChoiceField
     8from django.db.models.fields import BLANK_CHOICE_DASH
     9from django.forms import Form, ModelForm, FileField, ModelChoiceField, widgets
    910from django.forms.models import ModelFormMetaclass
    1011from django.test import TestCase
    1112from django.utils import six
     13from django.utils.encoding import smart_unicode
    1214
    1315from ..models import (ChoiceOptionModel, ChoiceFieldModel, FileModel, Group,
    14     BoundaryModel, Defaults)
     16    BoundaryModel, Defaults, Walk, WalkCategory)
    1517
    1618
    1719class ChoiceFieldForm(ModelForm):
    class RelatedModelFormTests(TestCase):  
    197199            model=A
    198200
    199201        self.assertTrue(issubclass(ModelFormMetaclass(b'Form', (ModelForm,), {'Meta': Meta}), ModelForm))
     202
     203
     204class WalkForm(ModelForm):
     205    class Meta:
     206        model = Walk
     207
     208class StrictWalkForm(WalkForm):
     209    class Meta(WalkForm.Meta):
     210        overrides = {
     211            'description': {
     212                'required': True,
     213            },
     214        }
     215
     216class TextareaWalkForm(WalkForm):
     217    class Meta(WalkForm.Meta):
     218        overrides = {
     219            (lambda f: getattr(f, 'max_length', 0) > 300): {
     220                'widget': widgets.Textarea,
     221            },
     222        }
     223
     224class HelpfulWalkForm(WalkForm):
     225    class Meta(WalkForm.Meta):
     226        overrides = {
     227            'silliness': {
     228                'help_text': 'Range: 0.0 -> 1.0',
     229            },
     230            'description': {
     231                'help_text': '(optional)',
     232            },
     233            'category': {
     234                'empty_label': '--[ choose one ]--',
     235            },
     236        }
     237
     238class ShortWalkForm(WalkForm):
     239    class Meta(WalkForm.Meta):
     240        overrides = {
     241            'name': {
     242                'label': 'Short name',
     243                'max_length': 50,
     244            },
     245        }
     246
     247class RelaxedWalkForm(WalkForm):
     248    class Meta(WalkForm.Meta):
     249        overrides = {
     250            'name': {
     251                'required': False,
     252            },
     253        }
     254
     255class ModelChoiceFieldWithKeys(ModelChoiceField):
     256    def label_from_instance(self, obj):
     257        return u'[%s] %s' % (
     258            smart_unicode(obj.pk),
     259            smart_unicode(obj))
     260
     261class FancyWalkForm(WalkForm):
     262    class Meta(WalkForm.Meta):
     263        overrides = {
     264            'category': {
     265                'form_class': ModelChoiceFieldWithKeys,
     266            },
     267        }
     268
     269
     270class OverridesTest(TestCase):
     271    def setUp(self):
     272        WalkCategory.objects.create(id=1, name='Long-legged')
     273        WalkCategory.objects.create(id=2, name='All-fours')
     274        self.normal_form = WalkForm()
     275        self.strict_form = StrictWalkForm()
     276        self.textarea_form = TextareaWalkForm()
     277        self.short_form = ShortWalkForm()
     278        self.relaxed_form = RelaxedWalkForm()
     279        self.help_form = HelpfulWalkForm()
     280        self.fancy_form = FancyWalkForm()
     281
     282    def test_required_false(self):
     283        self.assertTrue(self.normal_form.fields['name'].required)
     284        self.assertTrue(self.normal_form.fields['name'].widget.is_required)
     285
     286        self.assertFalse(self.relaxed_form.fields['name'].required)
     287        self.assertFalse(self.relaxed_form.fields['name'].widget.is_required)
     288
     289    def test_required_true(self):
     290        self.assertFalse(
     291            self.normal_form.fields['description'].required)
     292        self.assertFalse(
     293            self.normal_form.fields['description'].widget.is_required)
     294
     295        self.assertTrue(
     296            self.strict_form.fields['description'].required)
     297        self.assertTrue(
     298            self.strict_form.fields['description'].widget.is_required)
     299
     300    def test_max_length(self):
     301        self.assertEqual(self.normal_form.fields['name'].max_length, 255)
     302        self.assertEqual(
     303            self.normal_form.fields['name'].widget.attrs['maxlength'], '255')
     304
     305        self.assertEqual(self.short_form.fields['name'].max_length, 50)
     306        self.assertEqual(
     307            self.short_form.fields['name'].widget.attrs['maxlength'], '50')
     308
     309    def test_textarea(self):
     310        self.assertEqual(
     311            type(self.normal_form.fields['description'].widget),
     312            widgets.TextInput)
     313        self.assertEqual(
     314            type(self.textarea_form.fields['description'].widget),
     315            widgets.Textarea)
     316
     317    def test_helptext(self):
     318        self.assertRegexpMatches(
     319            self.normal_form.fields['silliness'].help_text, r'^0 = not silly')
     320        self.assertRegexpMatches(
     321            self.help_form.fields['silliness'].help_text, r'^Range: 0')
     322
     323        self.assertEqual(
     324            self.normal_form.fields['description'].help_text, '')
     325        self.assertEqual(
     326            self.help_form.fields['description'].help_text, '(optional)')
     327
     328    def test_empty_label(self):
     329        self.assertRegexpMatches(
     330            self.normal_form.fields['category'].empty_label, r'^\-+$')
     331        self.assertEqual(
     332            self.help_form.fields['category'].empty_label,
     333            '--[ choose one ]--')
     334
     335    def test_label(self):
     336        self.assertEqual(self.normal_form.fields['name'].label, 'Name')
     337        self.assertEqual(self.short_form.fields['name'].label, 'Short name')
     338
     339    def test_form_class(self):
     340        self.assertEqual(
     341            list(self.normal_form.fields['category'].choices)[1:],
     342            [(1, u'Long-legged'), (2, u'All-fours')]
     343        )
     344        self.assertEqual(
     345            list(self.fancy_form.fields['category'].choices)[1:],
     346            [(1, u'[1] Long-legged'), (2, u'[2] All-fours')]
     347        )
Back to Top