Form与ModelForm中的插件使用

一、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

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

基类中取出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)
value

所以,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的流程了。





原文地址:https://www.cnblogs.com/shenjianping/p/11572483.html