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
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属性去获取到关联的当前那条数据对象