一、Form插件的使用
(一)widget参数
from .models import * from django import forms from django.forms import widgets class BookForm(forms.Form): email=forms.EmailField() title = forms.CharField(max_length=32,label="书籍名称") price = forms.DecimalField(max_digits=4, decimal_places=2,label="价格") # 34.91 pub_date = forms.DateField(label="日期", widget=widgets.TextInput(attrs={"type":"date"}) #插件 ) book_type=forms.ChoiceField(choices=((1,"自然科学"),(2,"社会学科"),(3,"其他"))) publish=forms.ModelChoiceField(queryset=Publish.objects.all()) authors=forms.ModelMultipleChoiceField(queryset=Author.objects.all())
在date字段中使用了插件,参数是widget,可以从源码角度来看看是为什么?
class DateField(BaseTemporalField): widget = DateInput input_formats = formats.get_format_lazy('DATE_INPUT_FORMATS') default_error_messages = { 'invalid': _('Enter a valid date.'), } def to_python(self, value): """ Validate that the input can be converted to a date. Return a Python datetime.date object. """ if value in self.empty_values: return None if isinstance(value, datetime.datetime): return value.date() if isinstance(value, datetime.date): return value return super().to_python(value) def strptime(self, value, format): return datetime.datetime.strptime(value, format).date()
DateField的基类是Field,在Field中接收插件参数为widget:
class Field: widget = TextInput # Default widget to use when rendering this type ... ... def __init__(self, *, required=True, widget=None, label=None, initial=None, help_text='', error_messages=None, show_hidden_initial=False, validators=(), localize=False, disabled=False, label_suffix=None): self.required, self.label, self.initial = required, label, initial self.show_hidden_initial = show_hidden_initial self.help_text = help_text self.disabled = disabled self.label_suffix = label_suffix widget = widget or self.widget if isinstance(widget, type): widget = widget() else: widget = copy.deepcopy(widget) ... ... super().__init__()
显然,参数中传入的是widgets.TextInput,所以进入到widgets模块下找TextInput类:
class TextInput(Input): input_type = 'text' template_name = 'django/forms/widgets/text.html'
其基类是Input:
class Input(Widget): """ Base class for all <input> widgets. """ input_type = None # Subclasses must define this. template_name = 'django/forms/widgets/input.html' def __init__(self, attrs=None): if attrs is not None: attrs = attrs.copy() self.input_type = attrs.pop('type', self.input_type) super().__init__(attrs) def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) context['widget']['type'] = self.input_type return context
基类中取出input_type,并且拷贝attrs属性。
(二)触发widget渲染
渲染text.html,其实就是渲染input.html
<input type="{{ widget.type }}" name="{{ widget.name }}" {% if widget.value != None %} value="{{ widget.value|stringformat:'s' }}" {% endif %} {% include "django/forms/widgets/attrs.html" %} />
attrs.html
{% for name, value in widget.attrs.items %}
{% if value is not False %}
{{ name }}
{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}
{% endif %}
{% endfor %}
可是,在什么时候触发渲染呢?
其实就是在渲染每一BoundField对象(BoundField类位于django.forms.boundfield.BoundField),所以可以看看BoundField类中的方法:
class BoundField: "A Field plus data" def __init__(self, form, field, name): self.form = form self.field = field self.name = name self.html_name = form.add_prefix(name) self.html_initial_name = form.add_initial_prefix(name) self.html_initial_id = form.add_initial_prefix(self.auto_id) if self.field.label is None: self.label = pretty_name(name) else: self.label = self.field.label self.help_text = field.help_text or '' def __str__(self): """Render this field as an HTML widget.""" if self.field.show_hidden_initial: return self.as_widget() + self.as_hidden(only_initial=True) return self.as_widget() #在这里就和widgets联系上了 #调用widget中的render def as_widget(self, widget=None, attrs=None, only_initial=False): """ Render the field by rendering the passed widget, adding any HTML attributes passed as attrs. If a widget isn't specified, use the field's default widget. """ if not widget: widget = self.field.widget if self.field.localize: widget.is_localized = True attrs = attrs or {} attrs = self.build_widget_attrs(attrs, widget) auto_id = self.auto_id if auto_id and 'id' not in attrs and 'id' not in widget.attrs: if not only_initial: attrs['id'] = auto_id else: attrs['id'] = self.html_initial_id if not only_initial: name = self.html_name else: name = self.html_initial_name kwargs = {} if func_supports_parameter(widget.render, 'renderer') or func_accepts_kwargs(widget.render): kwargs['renderer'] = self.form.renderer else: warnings.warn( 'Add the `renderer` argument to the render() method of %s. ' 'It will be mandatory in Django 2.1.' % widget.__class__, RemovedInDjango21Warning, stacklevel=2, ) return widget.render( name=name, value=self.value(), attrs=attrs, **kwargs )
调用WIdget中的render(Widget位于django.forms.widgets.Widget):
def render(self, name, value, attrs=None, renderer=None): """Render the widget as an HTML string.""" context = self.get_context(name, value, attrs) return self._render(self.template_name, context, renderer) def _render(self, template_name, context, renderer=None): if renderer is None: renderer = get_default_renderer() return mark_safe(renderer.render(template_name, context))
从这里可以知道widget的name就是self.name,是forms字段名称,value是self.initial。
def value(self): """ Return the value for this BoundField, using the initial value if the form is not bound or the data otherwise. """ data = self.initial if self.form.is_bound: data = self.field.bound_data(self.data, data) return self.field.prepare_value(data)
所以,pub_date标签渲染为:
<input type="date" name="pub_date" >
二、ModelForm插件的使用
from django.forms import widgets as wid class BookForm(ModelForm): class Meta: model=Book fields="__all__" labels={"title":"书籍名称", "price":"价格"} widgets={ "title":wid.TextInput(attrs={"class":"form-control"}), "price":wid.TextInput(attrs={"class":"form-control"}), "pub_date":wid.TextInput(attrs={"class":"form-control","type":"date"}), "publish":wid.Select(attrs={"class":"form-control"}), "authors":wid.SelectMultiple(attrs={"class":"form-control"}), }
在ModelForm中使用widgets参数,看看源码中参数的定义:
#自定义ModelForm中Meta中的可传参数 class ModelFormOptions: def __init__(self, options=None): self.model = getattr(options, 'model', None) self.fields = getattr(options, 'fields', None) self.exclude = getattr(options, 'exclude', None) self.widgets = getattr(options, 'widgets', None) #插件参数 self.localized_fields = getattr(options, 'localized_fields', None) self.labels = getattr(options, 'labels', None) self.help_texts = getattr(options, 'help_texts', None) self.error_messages = getattr(options, 'error_messages', None) self.field_classes = getattr(options, 'field_classes', None)
另外,在自定义ModelForm的元类中已经生成了对应fields,并且每个字段与widget完成了映射。
class ModelFormMetaclass(DeclarativeFieldsMetaclass): def __new__(mcs, name, bases, attrs): ... ... #得到字段 fields = fields_for_model( opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback, opts.localized_fields, opts.labels, opts.help_texts, opts.error_messages, opts.field_classes, # limit_choices_to will be applied during ModelForm.__init__(). apply_limit_choices_to=False, ) ... ... new_class.base_fields = fields return new_class
这样剩下的可以参考Form中widget的流程了。