• python---django中form组件(2)自定制属性以及表单的各种验证,以及数据源的实时更新,以及和数据库关联使用ModelForm和元类


    自定义属性以及各种验证

    分析widget:

    class TestForm(forms.Form):
        user = fields.CharField(
                required = True,
                widget = widgets.TextInput()
        )

    追踪widgets.py

    __all__ = (
        'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'NumberInput',
        'EmailInput', 'URLInput', 'PasswordInput', 'HiddenInput',
        'MultipleHiddenInput', 'FileInput', 'ClearableFileInput', 'Textarea',
        'DateInput', 'DateTimeInput', 'TimeInput', 'CheckboxInput', 'Select',
        'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
        'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
        'SplitHiddenDateTimeWidget', 'SelectDateWidget',
    )
    全部控件
    class TextInput(Input):
        input_type = 'text'
        template_name = 'django/forms/widgets/text.html'  #模板html存放路径

    追踪父类:

    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):  #发现这里又参数attrs可以设置属性
            if attrs is not None:
                attrs = attrs.copy()
                self.input_type = attrs.pop('type', self.input_type)
            super(Input, self).__init__(attrs)
    
        def get_context(self, name, value, attrs):
            context = super(Input, self).get_context(name, value, attrs)
            context['widget']['type'] = self.input_type
            return context

    发现模板文件template_name = 'django/forms/widgets/input.html',实际上并不存在,是调用了父类方法

    class Widget(six.with_metaclass(RenameWidgetMethods)):
        def get_context(self, name, value, attrs):
            context = {}
            context['widget'] = {
                'name': name,
                'is_hidden': self.is_hidden,
                'required': self.is_required,
                'value': self.format_value(value),
                'attrs': self.build_attrs(self.attrs, attrs),
                'template_name': self.template_name,
            }
            return context
    
        def render(self, name, value, attrs=None, renderer=None):
            """
            Returns this Widget rendered as HTML, as a Unicode string.  #生成对于html代码,返回使用
            """
            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 = widgets.TextInput(attrs={"class":"c1"}),#这个属性,在前端进行设置就可以生成想要的样式,widgets代表显示的字段对象

    补充:在服务端生成原生字符串,不需要前端渲染时进行转义

    txt = "<input type='text' />"  #默认发送到模板页面是无法显示需要进行处理
    
    #在views中处理:
    fromdjango.utils.safestring import make_safe
    txt = mark_safe(txt)
    #前端可以正常显示

    select单选框:

    sel_inp=fields.ChoiceField(
        choices = [(1,'a'),(2,'b'),]
    )

    select框:

    sel_inp = fields.CharField(
            widget = widgets.Select(choices=[(1,'a'),(2,'b'),])
    )

    combo多选:

    radio_inp=fields.MultipleChoiceField(
        choices = [(1,'a'),(2,'b'),]   #含有multiple时可以写在外,也可以写在内,这里推荐在外
        widget = widgets.SelectMultiple(attrs={'class':"c1"})
    )

    单选CheckBox:

        chk_inp = fields.CharField(
            widget = widgets.CheckboxInput()
        )

    多选CheckBox

        mchk_inp = fields.MultipleChoiceField(
            widget = widgets.CheckboxSelectMultiple(),
            choices=[(1, "d"), (2, "e"),(3,'r') ],
            initial = [2,3]
        )

    radio单选:

        rad_inp = fields.ChoiceField(
            choices=[(1,""),(2,""),],
            initial=2,
            widget =widgets.RadioSelect(),
        )

     字段用于保存正则表达式,Html插件用于生产HTML标签(input)

     补充:为所有的字段控件设置属性(在__new__方法中获取base_fields,其中包含有字典数据{'字段名':字段对象,....})

    from django.forms import ModelForm
    from repository import models
    
    class CustomerForm(ModelForm):
        class Meta:
            model = models.CustumerInfo #将表与元类中的数据关联
            fields = "__all__"
    
        def __new__(cls, *args, **kwargs):
            print(cls.base_fields)
            #OrderedDict([('name', <django.forms.fields.CharField object at 0x00000000047FE9E8>), ('contact_type', <django.forms.fields.TypedChoiceField object at 0x00000000047FEBA8>), ('contact', <django.forms.fields.CharField object at 0x00000000047FECC0>), ('source', <django.forms.fields.TypedChoiceField object at 0x00000000047FEE10>), ('referral_from', <django.forms.models.ModelChoiceField object at 0x00000000047FEEF0>), ('consult_courses', <django.forms.models.ModelMultipleChoiceField object at 0x000000000480B048>), ('consult_content', <django.forms.fields.CharField object at 0x000000000480B0B8>), ('status', <django.forms.fields.TypedChoiceField object at 0x000000000480B208>), ('consultant', <django.forms.models.ModelChoiceField object at 0x000000000480B2E8>)])
            #这张表中的所有字段对象
            for field_name,field_obj in dict(cls.base_fields).items():
                field_obj.widget.attrs.update({'class':"form-control"})  #根据字段对象修改属性
    
    return ModelForm.__new__(cls)

    数据的实时更新:和数据库关联

    关联方法一

    数据库:

    class User(models.Model):
        username = models.CharField(max_length=32)
        email = models.CharField(max_length=32)

    form组件:

    from app02.models import User
    class InfoForm(forms.Form):
        def __init__(self,*args,**kwargs):   #保证了每次获取form时,都会更新数据源
            super(InfoForm,self).__init__(*args,**kwargs)
            self.fields['user'].widget.choices = User.objects.all().values_list('id', 'username')
    
            self.fields['email'].widget.choices = User.objects.all().values_list('id','email')
    
        email = fields.IntegerField(
            widget= widgets.Select(choices=User.objects.all().values_list('id','email'))
        )
    
        user = fields.IntegerField(
            widget=widgets.Select(choices=User.objects.all().values_list('id', 'username'))
        )

    views:

    from app02.forms import UserForm,InfoForm
    
    def users(req):
        obj = InfoForm()
        i1 = User.objects.all().values_list('id', 'username') #列表内部是元组
        i2 = User.objects.all().values('id', 'username')    #列表,内部是字典
        print(i1,type(i1))
        print(i2,type(i2))
        return render(req,"users.html",{"obj":obj})

    前端显示正常:

       <p>{{ obj.user }}</p>
       <p>{{ obj.email }}</p>

    关联方法二 

    另一种实时更新的方法:使用ModelChoiceField

    from app02.models import User
    from django.forms import ModelChoiceField
    
    class InfoForm(forms.Form):
        def __init__(self,*args,**kwargs):
            super(InfoForm,self).__init__(*args,**kwargs)
            self.fields['user'].widget.choices = User.objects.all().values_list('id', 'username')
    
            self.fields['email'].widget.choices = User.objects.all().values_list('id','email')
    
        email = fields.IntegerField(
            widget= widgets.Select(choices=User.objects.all().values_list('id','email'))
        )
    
        user_id = ModelChoiceField(
            queryset=User.objects.all(),
            to_field_name='id'
        )

    前端:

    <p>{{ obj.user_id }}</p>

    默认输出:

      <option value="" selected>---------</option>
      <option value="1">User object</option>
      <option value="2">User object</option>
      <option value="3">User object</option>
      <option value="4">User object</option>

    其中 User object 不是我们想要的,这依赖于models中的__str__方法

    class User(models.Model):
        username = models.CharField(max_length=32)
        email = models.CharField(max_length=32)
    
        def __str__(self):
            return self.username

    这时候输出正常

    但是不推荐这种方法,这与models关联太强,不利于解耦,推荐使用第一种

    关联方法三(ModelForm和元类)

    forms.py

    from django.forms import ModelForm
    from repository import models
    
    class CustomerForm(ModelForm):
        class Meta:
            model = models.CustumerInfo   #将表与元类中的数据关联
            fields = ['name','consultant','status','source']  #设置显示的字段

    views.py中使用

    form = forms.CustomerForm()
    return render(request,"table_obj_change.html",locals())

    前端使用:

    {{ form }}

    下面来查看ModelForm源码

    class ModelFormMetaclass(DeclarativeFieldsMetaclass):
        def __new__(mcs, name, bases, attrs):
            base_formfield_callback = None
            for b in bases:
                if hasattr(b, 'Meta') and hasattr(b.Meta, 'formfield_callback'):
                    base_formfield_callback = b.Meta.formfield_callback
                    break
    
            formfield_callback = attrs.pop('formfield_callback', base_formfield_callback)
    
            new_class = super(ModelFormMetaclass, mcs).__new__(mcs, name, bases, attrs)
    
            if bases == (BaseModelForm,):
                return new_class
    
            opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))
    
            # We check if a string was passed to `fields` or `exclude`,
            # which is likely to be a mistake where the user typed ('foo') instead
            # of ('foo',)
            for opt in ['fields', 'exclude', 'localized_fields']:
                value = getattr(opts, opt)
                if isinstance(value, six.string_types) and value != ALL_FIELDS:
                    msg = ("%(model)s.Meta.%(opt)s cannot be a string. "
                           "Did you mean to type: ('%(value)s',)?" % {
                               'model': new_class.__name__,
                               'opt': opt,
                               'value': value,
                           })
                    raise TypeError(msg)
    
            if opts.model:
                # If a model is defined, extract form fields from it.
                if opts.fields is None and opts.exclude is None:
                    raise ImproperlyConfigured(
                        "Creating a ModelForm without either the 'fields' attribute "
                        "or the 'exclude' attribute is prohibited; form %s "
                        "needs updating." % name
                    )
    
                if opts.fields == ALL_FIELDS:
                    # Sentinel for fields_for_model to indicate "get the list of
                    # fields from the model"
                    opts.fields = None
    
                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,
                )
    
                # make sure opts.fields doesn't specify an invalid field
                none_model_fields = [k for k, v in six.iteritems(fields) if not v]
                missing_fields = (set(none_model_fields) -
                                  set(new_class.declared_fields.keys()))
                if missing_fields:
                    message = 'Unknown field(s) (%s) specified for %s'
                    message = message % (', '.join(missing_fields),
                                         opts.model.__name__)
                    raise FieldError(message)
                # Override default model fields with any custom declared ones
                # (plus, include all the other declared fields).
                fields.update(new_class.declared_fields)
            else:
                fields = new_class.declared_fields
    
            new_class.base_fields = fields
    
            return new_class
    ModelFormMetaclass类源码
            opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None))  #opts代表ModelForm类中的元类,下面看看元类中可以设置那些数据
    
            # We check if a string was passed to `fields` or `exclude`,
            # which is likely to be a mistake where the user typed ('foo') instead
            # of ('foo',)
            for opt in ['fields', 'exclude', 'localized_fields']:  #这是元类中可以使用的属性fields:可以操作的字段,exclude:排除的字段
                value = getattr(opts, opt)
                if isinstance(value, six.string_types) and value != ALL_FIELDS:
                    msg = ("%(model)s.Meta.%(opt)s cannot be a string. "
                           "Did you mean to type: ('%(value)s',)?" % {
                               'model': new_class.__name__,
                               'opt': opt,
                               'value': value,
                           })
                    raise TypeError(msg)
    
            if opts.model:  #model属性用来关联数据表
                # If a model is defined, extract form fields from it.
                if opts.fields is None and opts.exclude is None:
                    raise ImproperlyConfigured(
                        "Creating a ModelForm without either the 'fields' attribute "
                        "or the 'exclude' attribute is prohibited; form %s "
                        "needs updating." % name
                    )
    
                if opts.fields == ALL_FIELDS:
                    # Sentinel for fields_for_model to indicate "get the list of
                    # fields from the model"
                    opts.fields = None
    
                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,
                )
    
                # make sure opts.fields doesn't specify an invalid field
                none_model_fields = [k for k, v in six.iteritems(fields) if not v]
                missing_fields = (set(none_model_fields) -
                                  set(new_class.declared_fields.keys()))
                if missing_fields:
                    message = 'Unknown field(s) (%s) specified for %s'
                    message = message % (', '.join(missing_fields),
                                         opts.model.__name__)
                    raise FieldError(message)
                # Override default model fields with any custom declared ones
                # (plus, include all the other declared fields).
                fields.update(new_class.declared_fields)
            else:
                fields = new_class.declared_fields
    
            new_class.base_fields = fields
    
            return new_class

     补充:

    1.fields若是设置为__all__则是代表所有的字段都去显示

    2.Form对象关联后可以使用instance属性去获取到关联的当前那条数据对象

  • 相关阅读:
    学习笔记 四边形不等式的模型
    题解 AT2230【Water Distribution】
    题解 CF848C 【Goodbye Souvenir】
    第二届全国大学生算法设计与编程挑战赛(春季赛)正式赛题解&比赛回顾
    积性函数相关学习笔记
    【MySQL】数据库备份
    【SpringMVC】SSM的maven依赖
    【SpringMVC】Jackson解决乱码问题配置
    【SpringMVC】DispatcherServlet与中文过滤器配置
    【Spring】学习笔记008--AOP实现方式
  • 原文地址:https://www.cnblogs.com/ssyfj/p/8681479.html
Copyright © 2020-2023  润新知