LoginSignup
3
0

More than 5 years have passed since last update.

Django:フォームのField順序は定義順が保持される?

Posted at

Django:フォームのField順序は定義順が保持される?

Djangoのフォームからフィールド定義を取得しようとして気になった。
fieldsプロパティでとれるやつ。
これってOrderedDictのオブジェクトだけどそのOrder(順番)って信じていいの?

公式ドキュメントを見る

ここ(Notes on field ordering)とかみるとFormで定義した順番でフィールドの表示も決まる。また、

By default Form.field_order=None, which retains the order in which you define the fields in your form class.

なんていう記述を見るとfieldsの順番もfield_orderで変更しない限りFormクラスの定義順を保持するように見える。

Djangoのソースを見る

なんとなく疑り深いだけの気もするのだけれどもうちょっと確証じみたものがほしい。
ってんでforms.pyのソースコード見ると、

forms.py

@html_safe
class BaseForm:
    """
    The main implementation of all the Form logic. Note that this class is
    different than Form. See the comments by the Form class for more info. Any
    improvements to the form API should be made to this class, not to the Form
    class.
    """
    default_renderer = None
    field_order = None
    prefix = None
    use_required_attribute = True

    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=None,
                 empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
        self.is_bound = data is not None or files is not None
        self.data = {} if data is None else data
        self.files = {} if files is None else files
        self.auto_id = auto_id
        if prefix is not None:
            self.prefix = prefix
        self.initial = initial or {}
        self.error_class = error_class
        # Translators: This is the default suffix added to form field labels
        self.label_suffix = label_suffix if label_suffix is not None else _(':')
        self.empty_permitted = empty_permitted
        self._errors = None  # Stores the errors after clean() has been called.

        # The base_fields class attribute is the *class-wide* definition of
        # fields. Because a particular *instance* of the class might want to
        # alter self.fields, we create self.fields here by copying base_fields.
        # Instances should always modify self.fields; they should not modify
        # self.base_fields.
        self.fields = copy.deepcopy(self.base_fields)

ん、ここでfieldsのインスタンスができてる。
deepcopyしているbase_fieldsを追ってくと、

forms.py

class DeclarativeFieldsMetaclass(MediaDefiningClass):
    """Collect Fields declared on the base classes."""
    def __new__(mcs, name, bases, attrs):
        # Collect fields from current class.
        current_fields = []
        for key, value in list(attrs.items()):
            if isinstance(value, Field):
                current_fields.append((key, value))
                attrs.pop(key)
        attrs['declared_fields'] = OrderedDict(current_fields)

        new_class = super(DeclarativeFieldsMetaclass, mcs).__new__(mcs, name, bases, attrs)

        # Walk through the MRO.
        declared_fields = OrderedDict()
        for base in reversed(new_class.__mro__):
            # Collect fields from base class.
            if hasattr(base, 'declared_fields'):
                declared_fields.update(base.declared_fields)

            # Field shadowing.
            for attr, value in base.__dict__.items():
                if value is None and attr in declared_fields:
                    declared_fields.pop(attr)

        new_class.base_fields = declared_fields
        new_class.declared_fields = declared_fields

        return new_class

    @classmethod
    def __prepare__(metacls, name, bases, **kwds):
        # Remember the order in which form fields are defined.
        return OrderedDict()

途中わかりずらいが、DeclarativeFieldsMetaclassというtypeを継承したメタクラスの__new__メソッドで、引数のattrsからFieldだけ抜き出してOrderedDictにセットしているように見える。

PEP520をチラ見する

でも、じゃあこのattrsの順番ってどうなるのって振り出しに戻るかに思われたところで、以下のやり取りをCode Examplesを見つけました。
[metaclass] 宣言したのと同じ順序でクラス属性を読み込む方法は?

回答によると、現在のPython(ver3.6~)ではクラスにおける定義順序は保持されているのだとのこと。。え?そうなの?
3.5より前ではメタクラスの__prepare__メソッドでcollections.OrderedDict()を返せばよい。

この__prepare__とは何か?
PEP520より抜粋。

The class attribute definition order is represented by the insertion order of names in the definition namespace. Thus, we can have access to the definition order by switching the definition namespace to an ordered mapping, such as collections.OrderedDict. This is feasible using a metaclass and __prepare__, as described above. In fact, exactly this is by far the most common use case for using __prepare__.

「クラス属性定義の順序は定義名前空間における名前の挿入順によってあらわされる。そういう訳で、われわれは定義順序に対して、例えばOrderdDictのような順序づけられたマッピングに定義空間を切り替えることによってアクセスできるようになる。このことは、上述のようにmetaclass__prepare__の使用によって可能となる。実際、これが__prepare__を使用する一番のユースケースなのである。」

なるほど。

結論

フォームのField順序は定義順が保持される?
――されてます。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0