• 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的流程了。





  • 相关阅读:
    如何在WinPE下安装xp安装版
    好用、功能强大的JQuery弹出层插件
    设计模式-旧话重提之类工厂的使用
    How can I manage Internet Explorer Security Zones via the registry?
    设计模式行为模式Behavioral Patterns()之FlexibleService模式
    how to design a new tree view control
    在C#中通过webdav操作exchange
    Yahoo! User Interface Library (哈偶然发现了这个东西)
    设计模式[2]旧话重提之工厂模式
    const和static readonly 的区别
  • 原文地址:https://www.cnblogs.com/shenjianping/p/11572483.html
Copyright © 2020-2023  润新知