Ticket #6630: 01-move-fields-and-exclude.diff

File 01-move-fields-and-exclude.diff, 12.8 KB (added by Petr Marhoun <petr.marhoun@…>, 14 years ago)
  • django/contrib/auth/forms.py

    diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py
    a b  
    2020
    2121    class Meta:
    2222        model = User
    23         fields = ("username",)
     23        fields = ("username", "password1", "password2")
    2424
    2525    def clean_username(self):
    2626        username = self.cleaned_data["username"]
  • django/forms/forms.py

    diff --git a/django/forms/forms.py b/django/forms/forms.py
    a b  
    2323        return u''
    2424    return name.replace('_', ' ').capitalize()
    2525
    26 def get_declared_fields(bases, attrs, with_base_fields=True):
     26def get_declared_fields(attrs):
    2727    """
    28     Create a list of form field instances from the passed in 'attrs', plus any
    29     similar fields on the base classes (in 'bases'). This is used by both the
    30     Form and ModelForm metclasses.
    31 
    32     If 'with_base_fields' is True, all fields from the bases are used.
    33     Otherwise, only fields in the 'declared_fields' attribute on the bases are
    34     used. The distinction is useful in ModelForm subclassing.
    35     Also integrates any additional media definitions
     28    Create a list of form field instances from the passed in 'attrs'. This is
     29    used by both the Form and ModelForm metaclasses.
    3630    """
    37     fields = [(field_name, attrs.pop(field_name)) for field_name, obj in attrs.items() if isinstance(obj, Field)]
     31    fields = [(field_name, attrs.pop(field_name)) for field_name, obj
     32        in attrs.items() if isinstance(obj, Field)]
    3833    fields.sort(lambda x, y: cmp(x[1].creation_counter, y[1].creation_counter))
    39 
    40     # If this class is subclassing another Form, add that Form's fields.
    41     # Note that we loop over the bases in *reverse*. This is necessary in
    42     # order to preserve the correct order of fields.
    43     if with_base_fields:
    44         for base in bases[::-1]:
    45             if hasattr(base, 'base_fields'):
    46                 fields = base.base_fields.items() + fields
    47     else:
    48         for base in bases[::-1]:
    49             if hasattr(base, 'declared_fields'):
    50                 fields = base.declared_fields.items() + fields
    51 
    5234    return SortedDict(fields)
    5335
    54 class DeclarativeFieldsMetaclass(type):
     36def get_base_fields(new_class):
     37    """
     38    Create a list of form field instances declared on the form and its base
     39    classes. This is used by both the Form and ModelForm metaclasses.
     40
     41    """
     42    # Note that we loop over the bases in *reverse*. This is necessary in order
     43    # to preserve the correct order of fields.
     44    fields = SortedDict()
     45    for base in reversed(new_class.mro()):
     46        if hasattr(base, 'declared_fields'):
     47            fields.update(base.declared_fields)
     48    return fields
     49
     50def select_fields(fields, opts):
     51    """
     52    Select some fields based on options.  This is used by both the Form and
     53    ModelForm metaclasses.
     54
     55    Option ``fields`` is an optional list of field names. If provided, only
     56    the named fields will be included in the returned fields and fields are
     57    sorted by it.
     58
     59    Option ``exclude`` is an optional list of field names. If provided,
     60    the named fields will be excluded from the returned fields, even if they
     61    are listed in the ``fields`` argument.
     62    """
     63    selected_fields = SortedDict()
     64    for field_name, field in fields.items():
     65        if opts.fields and not field_name in opts.fields:
     66            continue
     67        if opts.exclude and field_name in opts.exclude:
     68            continue
     69        selected_fields[field_name] = field
     70    if opts.fields:
     71        selected_fields = SortedDict((field_name, selected_fields.get(field_name))
     72            for field_name in opts.fields if field_name in selected_fields)
     73    return selected_fields
     74
     75class FormOptions(object):
     76    def __init__(self, options=None):
     77        self.fields = getattr(options, 'fields', None)
     78        self.exclude = getattr(options, 'exclude', None)
     79
     80class FormMetaclass(type):
    5581    """
    5682    Metaclass that converts Field attributes to a dictionary called
    57     'base_fields', taking into account parent class 'base_fields' as well.
     83    'base_fields', taking into account parent class fields as well.
     84    Also integrates any additional media definitions.
    5885    """
    5986    def __new__(cls, name, bases, attrs):
    60         attrs['base_fields'] = get_declared_fields(bases, attrs)
    61         new_class = super(DeclarativeFieldsMetaclass,
    62                      cls).__new__(cls, name, bases, attrs)
     87        attrs['declared_fields'] = get_declared_fields(attrs)
     88        new_class = super(FormMetaclass, cls).__new__(cls, name, bases, attrs)
     89        opts = new_class._meta = FormOptions(getattr(new_class, 'Meta', None))
     90        new_class.base_fields = select_fields(get_base_fields(new_class), opts)
    6391        if 'media' not in attrs:
    6492            new_class.media = media_property(new_class)
    6593        return new_class
     
    384412    # fancy metaclass stuff purely for the semantic sugar -- it allows one
    385413    # to define a form using declarative syntax.
    386414    # BaseForm itself has no way of designating self.fields.
    387     __metaclass__ = DeclarativeFieldsMetaclass
     415    __metaclass__ = FormMetaclass
    388416
    389417class BoundField(StrAndUnicode):
    390418    "A Field plus data"
  • django/forms/models.py

    diff --git a/django/forms/models.py b/django/forms/models.py
    a b  
    1212from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
    1313from django.core.validators import EMPTY_VALUES
    1414from util import ErrorList
    15 from forms import BaseForm, get_declared_fields
     15from forms import BaseForm, get_declared_fields, get_base_fields, select_fields
    1616from fields import Field, ChoiceField
    1717from widgets import SelectMultiple, HiddenInput, MultipleHiddenInput
    1818from widgets import media_property
     
    195195        self.exclude = getattr(options, 'exclude', None)
    196196        self.widgets = getattr(options, 'widgets', None)
    197197
    198 
    199198class ModelFormMetaclass(type):
    200199    def __new__(cls, name, bases, attrs):
    201200        formfield_callback = attrs.pop('formfield_callback',
    202201                lambda f, **kwargs: f.formfield(**kwargs))
    203         try:
    204             parents = [b for b in bases if issubclass(b, ModelForm)]
    205         except NameError:
    206             # We are defining ModelForm itself.
    207             parents = None
    208         declared_fields = get_declared_fields(bases, attrs, False)
    209         new_class = super(ModelFormMetaclass, cls).__new__(cls, name, bases,
    210                 attrs)
    211         if not parents:
    212             return new_class
    213 
    214         if 'media' not in attrs:
    215             new_class.media = media_property(new_class)
     202        attrs['declared_fields'] = get_declared_fields(attrs)
     203        new_class = super(ModelFormMetaclass, cls).__new__(cls, name, bases, attrs)
    216204        opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
    217205        if opts.model:
    218206            # If a model is defined, extract form fields from it.
    219             fields = fields_for_model(opts.model, opts.fields,
    220                                       opts.exclude, opts.widgets, formfield_callback)
     207            fields = fields_for_model(opts.model, widgets=opts.widgets,
     208                formfield_callback=formfield_callback)
    221209            # Override default model fields with any custom declared ones
    222210            # (plus, include all the other declared fields).
    223             fields.update(declared_fields)
     211            fields.update(get_base_fields(new_class))
    224212        else:
    225             fields = declared_fields
    226         new_class.declared_fields = declared_fields
    227         new_class.base_fields = fields
     213            fields = get_base_fields(new_class)
     214        new_class.base_fields = select_fields(fields, opts)
     215        if 'media' not in attrs:
     216            new_class.media = media_property(new_class)
    228217        return new_class
    229218
    230219class BaseModelForm(BaseForm):
  • tests/regressiontests/forms/forms.py

    diff --git a/tests/regressiontests/forms/forms.py b/tests/regressiontests/forms/forms.py
    a b  
    928928<tr><th>Field13:</th><td><input type="text" name="field13" /></td></tr>
    929929<tr><th>Field14:</th><td><input type="text" name="field14" /></td></tr>
    930930
     931It is possible to select some fields and reorder them.
     932>>> class OrderedTestForm(TestForm):
     933...    class Meta:
     934...        fields = ('field4', 'field2', 'field11', 'field8')
     935>>> p = OrderedTestForm(auto_id=False)
     936>>> print p
     937<tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr>
     938<tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr>
     939<tr><th>Field11:</th><td><input type="text" name="field11" /></td></tr>
     940<tr><th>Field8:</th><td><input type="text" name="field8" /></td></tr>
     941
     942Or to exlude some fields.
     943>>> class ExcludingTestForm(TestForm):
     944...    class Meta:
     945...        exclude = ('field14', 'field3', 'field5', 'field8', 'field1')
     946>>> p = ExcludingTestForm(auto_id=False)
     947>>> print p
     948<tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr>
     949<tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr>
     950<tr><th>Field6:</th><td><input type="text" name="field6" /></td></tr>
     951<tr><th>Field7:</th><td><input type="text" name="field7" /></td></tr>
     952<tr><th>Field9:</th><td><input type="text" name="field9" /></td></tr>
     953<tr><th>Field10:</th><td><input type="text" name="field10" /></td></tr>
     954<tr><th>Field11:</th><td><input type="text" name="field11" /></td></tr>
     955<tr><th>Field12:</th><td><input type="text" name="field12" /></td></tr>
     956<tr><th>Field13:</th><td><input type="text" name="field13" /></td></tr>
     957
     958Or to use fields and exlude at once.
     959>>> class CombinedTestForm(TestForm):
     960...    class Meta:
     961...        fields = ('field4', 'field2', 'field11', 'field8')
     962...        exclude = ('field14', 'field3', 'field5', 'field8', 'field1')
     963>>> p = CombinedTestForm(auto_id=False)
     964>>> print p
     965<tr><th>Field4:</th><td><input type="text" name="field4" /></td></tr>
     966<tr><th>Field2:</th><td><input type="text" name="field2" /></td></tr>
     967<tr><th>Field11:</th><td><input type="text" name="field11" /></td></tr>
     968
    931969Some Field classes have an effect on the HTML attributes of their associated
    932970Widget. If you set max_length in a CharField and its associated widget is
    933971either a TextInput or PasswordInput, then the widget's rendered HTML will
     
    13141352<li>Birthday: <input type="text" name="birthday" /></li>
    13151353<li>Instrument: <input type="text" name="instrument" /></li>
    13161354
    1317 Yes, you can subclass multiple forms. The fields are added in the order in
    1318 which the parent classes are listed.
     1355Yes, you can subclass multiple forms. The fields are added in the order given
     1356by the method resolution order.
    13191357>>> class Person(Form):
    13201358...     first_name = CharField()
    13211359...     last_name = CharField()
    13221360...     birthday = DateField()
    13231361>>> class Instrument(Form):
    13241362...     instrument = CharField()
    1325 >>> class Beatle(Person, Instrument):
     1363>>> class Beatle(Instrument, Person):
    13261364...     haircut_type = CharField()
    13271365>>> b = Beatle(auto_id=False)
    13281366>>> print b.as_ul()
     
    13321370<li>Instrument: <input type="text" name="instrument" /></li>
    13331371<li>Haircut type: <input type="text" name="haircut_type" /></li>
    13341372
     1373You can use also more complex inheritence.
     1374>>> class FormA(Form):
     1375...    field_a = CharField()
     1376>>> class FormB(Form):
     1377...    field_b = CharField()
     1378>>> class FormC(Form):
     1379...    field_c = CharField()
     1380>>> class FormD(FormB, FormC):
     1381...    field_d = CharField()
     1382>>> class FormE(FormC, FormA):
     1383...    field_e = CharField()
     1384>>> class FormF(FormD, FormE):
     1385...    field_f = CharField()
     1386>>> print FormF(auto_id=False).as_ul()
     1387<li>Field a: <input type="text" name="field_a" /></li>
     1388<li>Field c: <input type="text" name="field_c" /></li>
     1389<li>Field e: <input type="text" name="field_e" /></li>
     1390<li>Field b: <input type="text" name="field_b" /></li>
     1391<li>Field d: <input type="text" name="field_d" /></li>
     1392<li>Field f: <input type="text" name="field_f" /></li>
     1393
     1394But you can reorder fields by meta attributes.
     1395>>> class FormG(FormF):
     1396...    field_g = CharField()
     1397...    class Meta:
     1398...        fields = ('field_c', 'field_e', 'field_a', 'field_b', 'field_g')
     1399...        exclude = ('field_b', 'field_d')
     1400>>> print FormG(auto_id=False).as_ul()
     1401<li>Field c: <input type="text" name="field_c" /></li>
     1402<li>Field e: <input type="text" name="field_e" /></li>
     1403<li>Field a: <input type="text" name="field_a" /></li>
     1404<li>Field g: <input type="text" name="field_g" /></li>
     1405
    13351406# Forms with prefixes #########################################################
    13361407
    13371408Sometimes it's necessary to have multiple forms display on the same HTML page,
  • tests/regressiontests/forms/models.py

    diff --git a/tests/regressiontests/forms/models.py b/tests/regressiontests/forms/models.py
    a b  
    145145>>> f.is_valid()
    146146True
    147147>>> f.cleaned_data['name']
    148 u'Hello'
     148Traceback (most recent call last):
     149...
     150KeyError: 'name'
    149151>>> obj = f.save()
    150152>>> obj.name
    151153u'class default value'
Back to Top