多对多关系表的创建方式、forms组件
多对多关系表的三种创建方式
1.全自动,Django自动创建
class Book(models.Model):
title = models.CharField(max_length=20)
authors = models.ManyToManyField(to='Authors')
class Authors(models.Model):
name = models.CharField(max_length=32)
#好处:自始至终都没有操纵过第三张表,全部由orm创建,内置了四个操作第三张表的方法add、remove、set、clear
#不足:可扩展性差,自动创建的第三张表我发扩展和修改字段
2.纯手撸
class Book(models.Model):
title = models.CharField(max_length=32)
class Authors(models.Model):
name = models.CharField(max_length=32)
class Book2Authors(models.Model):
book = models.ForeignKey(to="Book")
author = models.ForeignKey(to="Authors")
create_time = models.DateField(auto_now_add = True)
#好处:第三张表中的字段名称和个数全部可以自己定义
#不足:不再支持orm跨表查询,不支持正反向查询的概念,不支持内置的第三张表操作的四个方法
3.半自动(推荐使用)
参数:
through:指定第三张表关系
through_fields:指定第三张表中哪两个字段维护表与表之间的多对多关系(这里有先后顺序,外键建在谁那里就先写谁)
class Book(models.Model):
title = models.CharField(max_length=20)
authors = models.ManyToManyField(to='Authors',through='Book2Author',through_fields=("book","authors"))
# 主键在谁那里谁就放前面
class Authors(models.Model):
name = models.CharField(max_length=32)
#books = models.ManyToManyField(to='Book',through='Book2Author',through_fields=('authors','book'))
class Book2Author(models.Model):
book = models.ForeignKey(to='Book')
#好处:可以任意的添加和修改第三张表中的字段,支持orm跨表查询
#不足:不支持add、remove、clear、set
forms组件
小例子
需求:1.写一个注册页面获取用户输入的用户名和密码,提交到后端之后,后端需要对用户名和密码进行校验,用户名不能含有‘xxx’密码不能少于三位
分析:
1.手动写HTML代码获取用户输入(渲染标签)
2.将数据传递给后端校验(校验数据)
3.如果数据有错误展示信息(展示信息)
#手动实现略
forms组件
forms组件能够做的就是上面的三件事情,在使用forms之前,我们需要先定义一个类:
from django import forms
class MyForm(forms.Form):
username = forms.CharField(max_length=8,min_length=3)
#指定username的长度是3-8位之间
password = forms.CharField(max_length=8,min_length=3)
email = forms.EmailField()
#输入的必须是email格式
其他字段及参数
label input对应的提示信息
initial input框默认值
required 默认为True控制字段是否必填
widget 给input框设置样式及属性
error_messages 设置报错信息
#widget的使用方法如下
widget=forms.widgets.TextInput({'class':'form-control c1 c2','username':'ylpb'})
#将input框类型设置成text,样式是'form-control c1 c2'
widget=forms.widgets.TextInput(attrs={'class':'form-control c1 c2','username':'ylpb'})
error_messages={
'max_length':'密码最长10位',
'min_length':'密码最短5位',
'required':'密码不能为空'
}
校验数据
# 1.给写好的类 传字典数据(待校验的数据)
form_obj = views.MyForm({'username':'ylpb','password':'12','email':'123'})
# 2.查看校验的数据是否合法
form_obj.is_valid()
False # 只有当你的数据全部符合校验规则的情况下 结果才是True 否则都为False
# 3.查看不符合规则的字段及错误的理由
form_obj.errors
{
'password': ['Ensure this value has at least 3 characters (it has 2).'],
'email': ['Enter a valid email address.']
}
# 4.查看符合校验规则的数据
form_obj.cleaned_data
{'username': 'jason'}
# 5.forms组件中 定义的字段默认都是必须传值的,不能少传,多传取前面的
form_obj = views.MyForm({'username':'ylpb','password':'12345'})
form_obj.is_valid()
False
form_obj.errors
{'email': ['This field is required.']}
# 6.forms组件只会校验forms类中定义的字段,如果你多传了,不会有任何影响
form_obj = views.MyForm({'username':'ylpb','password':'12345','email':'123@qq.com','xxx':'嘿嘿嘿'})
form_obj.is_valid()
True
渲染标签
forms组件只会帮你渲染获取用户输入的标签,不会帮你渲染提交按钮,需要你自己手动添加
<p>forms组件渲染标签方式1:封装程度太高,不推荐使用但是可以用在本地测试</p>
{{ form_obj.as_p }} <!--自动渲染所有input框 -->
{{ form_obj.as_ul }}
{{ form_obj.as_table }}
<p>forms组件渲染标签方式2:不推荐使用 写起来太复杂</p>
{{ form_obj.username.label }}{{ form_obj.username }}
{{ form_obj.username.label }}{{ form_obj.password }}
{{ form_obj.username.label }}{{ form_obj.email }}
<p>forms组件渲染标签方式3:推荐使用 </p>
{% for form in form_obj %}
<p>{{ form.label }}{{ form }}</p> <!--form 等价于方式2中的对象点字段名-->
{% endfor %}
展示信息(使用第三种方式渲染)
<form action="" method="post" novalidate>
{% for forms in form_obj %}
<p>
{{ forms.label }}{{ forms }}
<span>{{ forms.errors.0 }}</span><!--这里的forms.errors是一个个列表,.0拿到的是列表里面的内容-->
</p> <!--form 等价于你方式2中的对象点字段名-->
{% endfor %}
<input type="submit">
</form>
数据的校验通常前后端都必须有,但前端的校验若不经风,所以后端必须有校验,上面的forms浏览器会默认在前端对数据进行校验,我们需要先禁止浏览器的校验功能,方法是在form标签加上novalidate参数。
<form action="" method="post" novalidate>
校验器
后端对数据进行校验有两层,第一层是使用内置校验器进行校验,校验器的导入方式与校验方式如下:
from django.core.validators import RegexValidator
validators=[
RegexValidator(r'^[0-9]+$', '请输入数字'),
RegexValidator(r'^159[0-9]+$', '数字必须以159开头')
#这里通过正则对数据进行筛选
通过校验器对数据的合法性进行校验之后如果还需要对数据进行进一步校验,比如输入的字符中不能有某些数据等等,可以使用钩子函数进行数据校验。
钩子函数
局部钩子
局部钩子只对指定的某一个字段进行校验。
def clean_username(self):
username = self.cleaned_data.get('username')
if '123'in username:
self.add_error('username','测试一下')
#raise ValidationError('test')
#主动抛异常也会被局部钩子捕获
return username
全局钩子
def clean(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if not password == confirm_password:
self.add_error('confirm_password','两次密码不一致')
return self.cleaned_data
上述功能结合使用可以得到以下代码:
class MyForm(forms.Form):
username = forms.CharField(max_length=8,min_length=3,label='姓名',initial='ylpb',required=True,widget=forms.widgets.TextInput({'class':'form-control c1 c2','username':'ylpb'}))
#指定username的长度是3-8位之间
password = forms.CharField(max_length=8,min_length=3,label='密码',required=True,widget=forms.widgets.PasswordInput({'class':'form-control'}))
confirm_password = forms.CharField(max_length=8,min_length=3,label='确认密码',required=True,widget=forms.widgets.PasswordInput({'class':'form-control'}))
email = forms.EmailField(initial='abc@qq.com')
#输入的必须是email格式
gender = forms.ChoiceField(
choices=((1,'男'),(2,'女'),(3,'保密'))
)
def clean_username(self):
username = self.cleaned_data.get('username')
if '123'in username:
self.add_error('username','测试一下')
#raise ValidationError('test')
#主动抛异常也会被局部钩子捕获
return username
def clean(self):
password = self.cleaned_data.get('password')
confirm_password = self.cleaned_data.get('confirm_password')
if not password == confirm_password:
self.add_error('confirm_password','两次密码不一致')
return self.cleaned_data
#index是视图函数
def index(request):
form_obj = MyForm()
if request.method == 'POST':
form_obj = MyForm(request.POST)
if form_obj.is_valid():
#is_valid如果表单没有错误,则返回True,否则为False。 如果有错误被忽略,则返回False。
print(form_obj.cleaned_data)
return HttpResponse('数据正确')
return render(request,'index.html',locals())
forms组件常用字段与插件
initial初始值,input框里面的初始值。
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三" # 设置默认值
)
pwd = forms.CharField(min_length=6, label="密码")
error_messages重写错误信息。
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三",
error_messages={
"required": "不能为空",
"invalid": "格式错误",
"min_length": "用户名最短8位"
}
)
pwd = forms.CharField(min_length=6, label="密码")
password
class LoginForm(forms.Form):
...
pwd = forms.CharField(
min_length=6,
label="密码",
widget=forms.widgets.PasswordInput(attrs={'class': 'c1'}, render_value=True)
)
radioSelect单radio值为字符串
class LoginForm(forms.Form):
username = forms.CharField(
min_length=8,
label="用户名",
initial="张三",
error_messages={
"required": "不能为空",
"invalid": "格式错误",
"min_length": "用户名最短8位"
}
)
pwd = forms.CharField(min_length=6, label="密码")
gender = forms.fields.ChoiceField(
choices=((1, "男"), (2, "女"), (3, "保密")),
label="性别",
initial=3,
widget=forms.widgets.RadioSelect()
)
单选Select
class LoginForm(forms.Form):
...
hobby = forms.ChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
label="爱好",
initial=3,
widget=forms.widgets.Select()
)
多选Select
class LoginForm(forms.Form):
...
hobby = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
label="爱好",
initial=[1, 3],
widget=forms.widgets.SelectMultiple()
)
单选checkbox
class LoginForm(forms.Form):
...
keep = forms.ChoiceField(
label="是否记住密码",
initial="checked",
widget=forms.widgets.CheckboxInput()
)
多选checkbox
class LoginForm(forms.Form):
...
hobby = forms.MultipleChoiceField(
choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
label="爱好",
initial=[1, 3],
widget=forms.widgets.CheckboxSelectMultiple()
)
choice字段注意事项
在使用选择标签时,需要注意choices的选项可以配置从数据库中获取,但是由于是静态字段 获取的值无法实时更新,需要重写构造方法从而实现choice实时更新。
方式一
from django.forms import Form
from django.forms import widgets
from django.forms import fields
class MyForm(Form):
user = fields.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
initial=2,
widget=widgets.Select
)
def __init__(self, *args, **kwargs):
super(MyForm,self).__init__(*args, **kwargs)
# self.fields['user'].choices = ((1, '上海'), (2, '北京'),)
# 或
self.fields['user'].choices = models.Classes.objects.all().values_list('id','caption')
方式二
from django import forms
from django.forms import fields
from django.forms import models as form_model
class FInfo(forms.Form):
authors = form_model.ModelMultipleChoiceField(queryset=models.NNewType.objects.all()) # 多选
# authors = form_model.ModelChoiceField(queryset=models.NNewType.objects.all()) # 单选
Django Form所有内置字段
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
validators=[], 自定义验证规则
localize=False, 是否支持本地化
disabled=False, 是否可以编辑
label_suffix=None Label内容后缀
CharField(Field)
max_length=None, 最大长度
min_length=None, 最小长度
strip=True 是否移除用户输入空白
IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值
FloatField(IntegerField)
...
DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 总长度
decimal_places=None, 小数位长度
BaseTemporalField(Field)
input_formats=None 时间格式化
DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
DurationField(Field) 时间间隔:%d %H:%M:%S.%f
...
RegexField(CharField)
regex, 自定制正则表达式
max_length=None, 最大长度
min_length=None, 最小长度
error_message=None, 忽略,错误信息使用 error_messages={'invalid': '...'}
EmailField(CharField)
...
FileField(Field)
allow_empty_file=False 是否允许空文件
ImageField(FileField)
...
注:需要PIL模块,pip3 install Pillow
以上两个字典使用时,需要注意两点:
- form表单中 enctype="multipart/form-data"
- view函数中 obj = MyForm(request.POST, request.FILES)
URLField(Field)
...
BooleanField(Field)
...
NullBooleanField(BooleanField)
...
ChoiceField(Field)
...
choices=(), 选项,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 插件,默认select插件
label=None, Label内容
initial=None, 初始值
help_text='', 帮助提示
ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查询数据库中的数据
empty_label="---------", # 默认空显示内容
to_field_name=None, # HTML中value的值对应的字段
limit_choices_to=None # ModelForm中对queryset二次筛选
ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField
TypedChoiceField(ChoiceField)
coerce = lambda val: val 对选中的值进行一次转换
empty_value= '' 空值的默认值
MultipleChoiceField(ChoiceField)
...
TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 对选中的每一个值进行一次转换
empty_value= '' 空值的默认值
ComboField(Field)
fields=() 使用多个验证,如下:即验证最大长度20,又验证邮箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
MultiValueField(Field)
PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用
SplitDateTimeField(MultiValueField)
input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
FilePathField(ChoiceField) 文件选项,目录下文件显示在页面中
path, 文件夹路径
match=None, 正则匹配
recursive=False, 递归下面的文件夹
allow_files=True, 允许文件
allow_folders=False, 允许文件夹
required=True,
widget=None,
label=None,
initial=None,
help_text=''
GenericIPAddressField
protocol='both', both,ipv4,ipv6支持的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用
SlugField(CharField) 数字,字母,下划线,减号(连字符)
...
UUIDField(CharField) uuid类型
Django Form内置字段
字段校验
RegexValidator验证器
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
class MyForm(Form):
user = fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
)
自定义验证函数
import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
# 自定义验证规则
def mobile_validate(value):
mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
if not mobile_re.match(value):
raise ValidationError('手机号码格式错误')
class PublishForm(Form):
title = fields.CharField(max_length=20,
min_length=5,
error_messages={'required': '标题不能为空',
'min_length': '标题最少为5个字符',
'max_length': '标题最多为20个字符'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': '标题5-20个字符'}))
# 使用自定义验证规则
phone = fields.CharField(validators=[mobile_validate, ],
error_messages={'required': '手机不能为空'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': u'手机号码'}))
email = fields.EmailField(required=False,
error_messages={'required': u'邮箱不能为空','invalid': u'邮箱格式错误'},
widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'邮箱'}))
forms组件源码分析
我们的源码分析从is_valid方法开始,一起来看is_valid的源码
def is_valid(self):
"""
Returns True if the form has no errors. Otherwise, False. If errors are
being ignored, returns False.
"""
return self.is_bound and not self.errors
#这里可以看出,如果self.is_bound为true和self.errors是false才可以return,
接下来看self.is_bound:
如果我们输入了参数那么self.is_bound一定为true,接下来我们看self.errors,这里需要说明self是我们自定义的类实例化的对象。
def errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors
#下面是self._errors源码,self._errors的默认值是none,由此可知errors一定执行self.full_clean()
self._errors = None # Stores the errors after clean() has been called.
#self.full_clean()源码
def full_clean(self):
"""
Cleans all of self.data and populates self._errors and
self.cleaned_data.
"""
self._errors = ErrorDict()#一个空字典
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
#最关键的三个部分
self._clean_fields()
self._clean_form()
self._post_clean()
下面我们来分别看这三个部分分别有什么功能
self._clean_fields()
def _clean_fields(self):
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
#value就是用户传入的数据值
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
value = field.clean(value)
#这里的clean是钩子函数,将校验通过的数据添加到字典中
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
#利用反射判断我们是否定义了钩子函数,如果有,自动触发
value = getattr(self, 'clean_%s' % name)()#这里的name就是一个个字段名
#这里的value是钩子函数的返回值
self.cleaned_data[name] = value
except ValidationError as e:
#因为上面使用了局部钩子,所以如果出现ValidationError错误也会先被局部钩子捕获,而使程序不会抛异常
self.add_error(name, e)
#如果数据报错就添加到这里,因为这里有异常捕获所以不会报错
self._clean_form()
def _clean_form(self):
try:
cleaned_data = self.clean()
#cleaned_data是全局钩子返回的内容
#调用我们自己的clean方法,如果我们没写这调用类的
except ValidationError as e:
self.add_error(None, e)
else:
if cleaned_data is not None:
self.cleaned_data = cleaned_data
#这里诠释了全局钩子是如何自动调用的
self._post_clean()里面没有内容,我们的源码之旅到此结束。
通过看源码我们发现局部钩子和全局钩子分别通过反射和对象属性方法的查找顺序两种方式实现的自动调用。
多对多关系表的创建方式、forms组件的更多相关文章
- 多对多的三种创建方式-forms相关组件-钩子函数-cookie与session
多对多的三种创建方式 1.全自动(推荐使用的**) 优势:第三张可以任意的扩展字段 缺点:ORM查询不方便,如果后续字段增加更改时不便添加修改 manyToManyField创建的第三张表属于虚拟的, ...
- Django-多对多关系的三种创建方式-forms组件使用-cookie与session-08
目录 表模型类多对多关系的三种创建方式 django forms 组件 登录功能手写推理过程 整段代码可以放过来 forms 组件使用 forms 后端定义规则并校验结果 forms 前端渲染标签组件 ...
- ORM中choices参数(重要)、MTV于MVC模型、多对多关系三种创建方式
choices参数(重要) **使用方式
- 用SQLAlchemy创建一对多,多对多关系表
多对多关系表的创建: 如果建立好多对多关系后,我们就可以通过关系名进行循环查找,比如laowang = Teacher.query.filter(Teacher.name=='laowang').fi ...
- Django-website 程序案例系列-7 创建多对多关系表
创建多对多关系表: 方式一:一共三张表其中有一张中间表需要手工建立(建议使用第一种方式自定制程度高) class Host(models.Model): hostname = models.CharF ...
- Django框架(十)--ORM多对多关联关系三种创建方式、form组件
多对多的三种创建方式 1.全自动(就是平常我们创建表多对多关系的方式) class Book(models.Model): title = models.CharField(max_length=32 ...
- 多对多第三张表的创建方式 和 forms组件的使用
目录 一.多对多第三张表的创建 1. 全自动方式 (1)实现代码 (2)优点和不足 2. 纯手撸方式(了解) (1)实现代码 (2)优点和不足 3. 半自动方式(推荐使用) (1)实现代码 (2)优点 ...
- Django --- 多对多关系创建,forms组件
目录 多对多三种创建方式 1.系统直接创建 2.自己手动创建 3.自己定义加与系统创建 forms组件 1. 如何使用forms组件 2. 使用forms组件校验数据 3. 使用forms组件渲染标签 ...
- 多对多表创建、forms组件、cookie与session
多对多表的三种创建方式 1.全自动(较为推荐) 优势:不需要你手动创建第三张表 不足:由于第三张表不是你手动创建的,所以表字段是固定的无法扩展 class Book(models.Model): ti ...
随机推荐
- VS调试异常代码 HRESULT:0x80070057 (E_INVALIDARG)解决方法
我目前在做的一个系统是VS2010写的的B/S架构程序, 主要技术是:C#.SQLSERVER2008.NHibernate,Python,Nhibernate 的*.hbn.xml是映射数据库的表结 ...
- 中国MOOC_零基础学Java语言_第2周 判断_2信号报告
2 信号报告(5分) 题目内容: 无线电台的RS制信号报告是由三两个部分组成的: R(Readability) 信号可辨度即清晰度. S(Strength) 信号强度即大小. 其中R位于报告第一 ...
- 程序的内存分布 - 以 Linux 为例,基于 C 语言分析
这里以 Linux 为例,用 C 语言进行演示. 内存模型 - 内存空间名称 内容 读写操作 分配时机 高地址 kernel 内核空间 命令行参数.环境变量等 不可读写 程序运行时 - stack 栈 ...
- zstack分配的虚拟机的dns设置
环境: $ uname -a Linux 10-57-19-61 2.6.32-504.el6.x86_64 #1 SMP Wed Oct 15 04:27:16 UTC 2014 x86_64 x8 ...
- java通过jna调用so
c++: FirstEliteValidate.h #pragma once void __attribute__((constructor)) startup();void __attribute_ ...
- PHP 数组函数 内部指针
current( &$arr ) 每个数组的当前单元,初始值的 数组的第一个单元next ( &$arr ) 返回数组中的下一个单元 , 如果没值则返回falshprev ( & ...
- 测试需要了解的技术之基础篇三__持续集成持续交付DevOps
持续集成.持续交付.DevOps 1.容器技术Docker:容器技术介绍.Docker安装与加速配置.Docker基础命令.Docker搭建selenium.Docker搭建持续集成平台Jenkins ...
- 【SQL系列】从SQL语言的分类谈COMMIT和ROLLBACK的用法
公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[SQL系列]从SQL语言的分类谈COMMIT和 ...
- python 封装dlib模型进行人脸识别系统的登录认证
1.直接上干货 #!/usr/bin/python # -*- coding: utf-8 -*- import time import dlib import numpy as np class f ...
- Spring Security Session Time Out
最近在用Spring Security做登录管理,登陆成功后,页面长时间无操作,超过session的有效期后,再次点击页面操作,页面无反应,需重新登录后才可正常使用系统. 为了优化用户体验,使得在se ...