#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のソースコード見ると、
@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を追ってくと、
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順序は定義順が保持される?
――されてます。