at_yasu's blog

ロード的なことを

newformのメモ

Djangoで、HTMLのfieldの属性等を管理するクラスFieldのメモ。

HTMLのFieldの属性はwidgetというクラスで管理し、Fieldオブジェクトはwidgetオブジェクトを呼び出してごにょごにょするみたい。


基底クラスであるFieldは、下記のような仕様になっている。

コンストラクタの引数は下記

required
Boolean値。デフォルト:Treu。どう使うのかわからない。
widget
widgetオブジェクト。たいていはTextInputオブジェクトだが、すきなように使うことができる
label
ラベル。そのまま。"pretty" versionってなんぞ・・・?
initial
表示の時に使う名前
help_text
そのまま。ヘルプテキスト。

インスタンスメソッドは二つしか使わない。

clean
HTMLで入力した値が正しいかどうか判断する時に使われる関数。正しくないとき等は、例外の「ValidationError」 を呼び出せば良い。
widget_attr
HTMLのフォームの属性をwidgetに与える関数。返り値に辞書型で返せばそのまま出力する。
class Field(object):
	widget = TextInput # Default widget to use when rendering this type of Field.
	hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden".
		
	# Tracks each time a Field instance is created. Used to retain order.
	creation_counter = 0
			
	def __init__(self, required=True, widget=None, label=None, initial=None, help_text=None):
		# required -- Boolean that specifies whether the field is required.
		#			 True by default.
		# widget -- A Widget class, or instance of a Widget class, that should be
		#		 used for this Field when displaying it. Each Field has a default
		#		 Widget that it'll use if you don't specify this. In most cases,
		#		 the default widget is TextInput.
		# label -- A verbose name for this field, for use in displaying this field in
		#		 a form. By default, Django will use a "pretty" version of the form
		#		 field name, if the Field is part of a Form.
		# initial -- A value to use in this Field's initial display. This value is
		#			*not* used as a fallback if data isn't given.
		# help_text -- An optional string to use as "help text" for this Field.
	
	def clean(self, value):
		"""
		Validates the given value and returns its "cleaned" value as an
		appropriate Python object.

		Raises ValidationError for any errors.
		"""

	def widget_attrs(self, widget):
		"""
		Given a Widget instance (*not* a Widget class), returns a dictionary of
		any HTML attributes that should be added to the Widget, based on this
		Field.
		"""


CharFieldは下記のように使っている。コンストラクタのmax_length,min_lengthが追加されており、cleanでそれが使われている事にも注目。

class CharField(Field):
	def __init__(self, max_length=None, min_length=None, *args, **kwargs):
		self.max_length, self.min_length = max_length, min_length
		super(CharField, self).__init__(*args, **kwargs)

	def clean(self, value):
		"Validates max_length and min_length. Returns a Unicode object."
		super(CharField, self).clean(value)
		if value in EMPTY_VALUES:
			return u''
		value = smart_unicode(value)
		if self.max_length is not None and len(value) > self.max_length:
			raise ValidationError(gettext(u'Ensure this value has at most %d characters.') % self.max_length)
		if self.min_length is not None and len(value) < self.min_length:
			raise ValidationError(gettext(u'Ensure this value has at least %d characters.') % self.min_length)
		return value

	def widget_attrs(self, widget):
		if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)):
			return {'maxlength': str(self.max_length)}


一方でwidgetの方は下記になっている。

コンストラクタ
引数はattrという物だけ。早い話、HTMLのフォームの属性。
render
HTMLに整形したUnicodeの文字列を返すようにしたら良い。つまり、HTMLに出力される際に呼び出される。
build_attrs
属性の辞書を整形する際に呼び出される補助関数
value_from_datadict
widgetの名前と辞書型のデータが与えられ、widgetの値を返すようにする為の関数。らしい・・・
id_for_label
HTMLのID属性を、このwidgetの名前を使って作成する。
class Widget(object):
	is_hidden = False		  # Determines whether this corresponds to an <input type="hidden">.
	
	def __init__(self, attrs=None):
		self.attrs = attrs or {}
	
	def render(self, name, value, attrs=None):
		"""
		Returns this Widget rendered as HTML, as a Unicode string.
		
		The 'value' given is not guaranteed to be valid input, so subclass
		implementations should program defensively.
		"""
		raise NotImplementedError

	def build_attrs(self, extra_attrs=None, **kwargs):
		"Helper function for building an attribute dictionary."
		attrs = dict(self.attrs, **kwargs)
		if extra_attrs:
			attrs.update(extra_attrs)
		return attrs
		
	def value_from_datadict(self, data, name):
		"""
		Given a dictionary of data and this widget's name, returns the value
		of this widget. Returns None if it's not provided.
		"""
		return data.get(name, None)

	def id_for_label(self, id_):
		"""
		Returns the HTML ID attribute of this Widget for use by a <label>,
		given the ID of the field. Returns None if no ID is available.

		This hook is necessary because some widgets have multiple HTML
		elements and, thus, multiple IDs. In that case, this method should
		return an ID value that corresponds to the first ID in the widget's
		tags.
		"""
		return id_
	id_for_label = classmethod(id_for_label)

CharFieldが使っているInputwidgetは下記のようになっている。ほとんど使い回し出来るかも・・・

class Input(Widget):
	"""
	Base class for all <input> widgets (except type='checkbox' and
	type='radio', which are special).
	"""
	input_type = None # Subclasses must define this.

	def render(self, name, value, attrs=None):
		if value is None: value = ''
		final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
		if value != '': final_attrs['value'] = smart_unicode(value) # Only add the 'value' attribute if a value is non-empty.
		return u'<input%s />' % flatatt(final_attrs)