django杂项 (二)
Django 反向查找(related_name)
先定义两个模型,一个是A,一个是B,是一对多的类型
class` `A(models.Model):`` ``name``=` `models.CharField(``'名称'``, max_length``=``32``)` `class` `B(models.Model):`` ``a``=` `models.ForeignKey(A, verbose_name``=``'A类'``,related_name ``=` `"test"``)`` ``name ``=` `models.CharField(``'称呼'``, max_length``=``16``)
如果我们要查询一个A拥有哪些B的话
通过子表来查询主表
B.objects.``filter``(a``=``A_id).order_by(``'-created'``)
通过主表来查询子表
1、
A.objects.get(``id``=``A_id).test.``all``().order_by(``'-created'``)
2、
# Django默认每个主表对象都有一个外键的属性``# 可以通过它来查询所有属于主表的子表信息``# 查询方式:主表.子表_set()``# 返回值为一个queryset对象``A.``object``.get(``id``=``A_id).B_set().``all``().order_by(``'-created'``)
Django ManyToManyField.through_fields
示例:
from` `django.db ``import` `models` `class` `Person(models.Model):`` ``name ``=` `models.CharField(max_length``=``50``)` `class` `Group(models.Model):`` ``name ``=` `models.CharField(max_length``=``128``)`` ``members ``=` `models.ManyToManyField(Person, through``=``'Membership'``, through_fields``=``(``'group'``, ``'person'``))` `class` `Membership(models.Model):`` ``group ``=` `models.ForeignKey(Group)`` ``person ``=` `models.ForeignKey(Person)`` ``inviter ``=` `models.ForeignKey(Person, related_name``=``"membership_invites"``)`` ``invite_reason ``=` `models.CharField(max_length``=``64``)
成员对Person(person和inviter)有两个外键,这使关系不明确,Django无法知道要使用哪一个。在这种情况下,您必须明确指定Django应该使用through_fields使用哪些外键,如上例所示。 through_fields接受一个2元组('field1','field2'),其中field1是定义ManyToManyField的模型外键的名称(本例中为group PS:即因为"ManyToManyField"在Group上),field2是外键的名称目标模型(在这种情况下为人 PS:即因为"Person"是被关联的目标)。
如果在中介模型上有多个外键用于参与多对多关系的任何模型(或甚至两个模型),则必须指定through_fields。这也适用于使用中介模型时的递归关系,并且模型中有两个以上的外键,或者您想明确指定哪两个Django应该使用。 使用中间模型的递归关系总是被定义为非对称 - 也就是说,对称=假 - 因此,存在“源”和“目标”的概念。在这种情况下,'field1'将被视为关系的“源”,'field2'将被视为“目标”。
Django的ManyToManyField(多对多)中的through的作用
创建一个经典的多对多关系:一本书可以有多个作者,一个作者可以有多本书(如下)
运行“python manage.py makemigratons"和"python manage.py migrate”进行数据迁移之后,
会生成三张表,一个是book(书籍)表包含id,title两个字段,一个是author(作者表)包含id,name两个字段,这是我们刚刚在models.py文件中创建两个模型,但是有一点需要注意的是在book表里面没有我们创建的authors表,而是多了一个book_authors表,在这张表里面又多了两个字段book_id,author_id,其实这个第三张表就是用来存放书籍和作者之间映射关系的中间表
那么我们如何进行数据的查询呢?
1.一本书的所有作者
b = Book.objects.get(id=1) #拿到具体哪本书
b.author.all()
2.一个作者的所有书籍:
a = Author.objects.get(id=1)
a.book_set.all() #反向查询
但是如果我们想要搜集关于这个作者发布某一本书籍的时间额外增加一个字段,或者说与现有的系统集成,这个关系表已经存在了,那对于这样的情形,Django允许指定一个用于管理多对多关系的中间模型,然后就可以把这些额外的字段添加到这个中间模型中,具体的方法就是在ManyToMany字段中指定through参数作为中介的中间模型,修改上models.py:
仔细观察BookAuthor这个类,也就是我们前面讲到的中间模型,同时我们看到在创建中间模型的时候我们创建了两个外键,这两个外键定义了两个模型之间是如何关联到一起的, 所以当创建多对多关系模型的时候提倡使用through参数去指定并创建中间模型,这样比较方便我们进行字段的扩展
那么此时我们又该如何添加和删除多对多关系呢?还能使用和刚刚相同的方法吗?
# 添加作者 ringo ringo = Author.objects.create(name='Ringo',email='ringo@qq.com') # 添加作者paul paul = Author.objects.create(name='Paul',email='paul@qq.com') # 添加书籍 python book1 book1 = Book.objects.create(title='python book1') # 给多对多添加值也就是添加多对多关系 m1 = BookAuthor(author=ringo,book=book1) # 第二种添加方式 m2 = BookAuthor.objects,create(author=paul,book=book1)
当我们使用多对多的中间模型之后,add(),remove(),create()这些方法都会被禁用,所以在创建这种类型的关系的时候唯一的方法就是通过创建中间模型的实例
clean()方法的简单应用
clean()方法主要用于验证相互依赖的字段,例如注册时,填写的“密码”和“确认密码”要相等时才符合要求。
在调用表单clean()
方法的时候,所有字段的验证方法已经执行完(表单字段的默认验证(如CharField())和特定字段属性的验证(clean_self.cleaned_data
填充的是目前为止已经合法的数据。所以你需要记住这个事实,你需要验证的字段可能没有通过初试的字段检查。(例如你要验证“密码”和“确认密码”是否相等,但或许它们没有通过初始字段检查,例如格式错误等,但仍会执行这一步)
在这一步,有两种方法报告错误。你可以在clean()
方法中抛出ValidationError
来创建错误。例如:
forms.py 1 from django import forms
2 class RegisterForms(BaseForm, forms.Form):
3 password = forms.CharField()
4 confirm_password = forms.CharField()
5
6 def clean(self):
7 cleaned_data = super(RegisterForms, self).clean() # 注意,super(RegisterForms, self).clean() 的调用时为了保证维持父类中的验证逻辑。
8 v1 = cleaned_data.get('password')
9 v2 = cleaned_data.get('confirm_password')
10 if v1 == v2:
11 pass
12 else:
13 raise ValidationError(message='密码输入不一致', code='invalid') #错误信息保存在errors.__all__中
1 view.py
2 def register(request):
3 if request.method == 'GET':
4 return render(request, 'register.html')
5
6 elif request.method == 'POST':
7 obj = RegisterForms(request=request, data=request.POST)
8 if obj.is_valid():
9 pass
10 else:
11 error = obj.errors
12 return render(request, 'register.html', {'errors': error, 'obj':obj}) #因为要在HTML中把errors.__all__显示出来,所以要把实例化的obj传过去
register.html 1 <form method='post' action='/register.html'>
2 <div class="form-group">
3 <label for="password">密码</label>
4 <input type="password" class="form-control" id="password" name="password" placeholder="请输入密码">
5 <p>
6 {{ errors.password.0 }}
7 {{ obj.non_field_errors.0 }} #显示共有的错误信息
8 </p>
9 </div>
10
11 </form>
第二种方法涉及将错误消息关联到某个字段。在这种情况下,让我们在表单的显示中关联一个错误信息到“password” 。例如:
1 from django import forms
2
3 class RegisterForms(BaseForm, forms.Form):
4 password = forms.CharField()
5 confirm_password = forms.CharField()
6
7 def clean(self):
8 cleaned_data = super(RegisterForms, self).clean()
9 v1 = cleaned_data.get('password')
10 v2 = cleaned_data.get('confirm_password')
11 if v1 == v2:
12 pass
13 else:
14 msg = '密码输入不一致'
15 self.add_error('password', msg)
此时错误信息是关联到password上的,因为不用传递实例化的obj,在register.html上也只需写上"{{ errors.password.0 }}
表格和字段验证
当调用方法"is_valid()"时会进行表单验证。如果要自定义此过程,可以进行各种更改,每个更改用于不同的目的。在表格处理期间运行三种类型的cleaning方法。这些通常在"is_valid()"
在表单上调用方法时执行。
通常来说,如果数据有错的话,任何cleaning方法都可以触发 ValidationError;如果数据没问题,那么该方法会把cleaned后的数据转化为Python object返回。
表单的验证分为几个步骤,可以自定义或覆盖:
1、Field上的to_python()方法是每次验证的第一步。
它将值强制转换为正确的数据类型,如果不可能,则引发ValidationError。此方法接受窗口小部件的原始值并返回转换后的值。例如,FloatField会将数据转换为Python float或引发ValidationError。 Field上的validate()方法处理不适合验证器的特定于字段的验证。它需要一个已被强制转换为正确数据类型的值,并在任何错误上引发ValidationError。此方法不返回任何内容,不应更改该值。您应该覆盖它以处理您不能或不想放入验证器的验证逻辑。
2、Field上的run_validators()方法运行所有字段的验证器,并将所有错误聚合到单个ValidationError中。
您不应该重写此方法。 Field子类上的clean()方法负责以正确的顺序运行to_python(),validate()和run_validators()并传播它们的错误。如果在任何时候,任何方法引发ValidationError,则验证将停止并引发该错误。此方法返回干净数据,然后将其插入到表单的cleaning_data字典中。 在表单子类上调用clean_
3、表单子类的clean()方法可以执行需要访问多个表单字段的验证。
您可以在此处进行检查,例如“如果提供了字段A,则字段B必须包含有效的电子邮件地址”。如果愿意,此方法可以返回完全不同的字典,该字典将用作cleaning_data。 由于在调用clean()时运行了字段验证方法,因此您还可以访问表单的errors属性,该属性包含清除单个字段引起的所有错误。 请注意,Form.clean()覆盖引发的任何错误都不会与任何字段相关联。它们进入一个特殊的“字段”(称为all),如果需要,可以通过non_field_errors()方法访问它。如果要将错误附加到表单中的特定字段,则需要调用add_error()。 另请注意,重写ModelForm子类的clean()方法时需要特别注意。 这些方法按上面给出的顺序运行,一次一个字段。也就是说,对于表单中的每个字段(按照它们在表单定义中声明的顺序),运行Field.clean()方法(或其覆盖),然后运行clean_
关于创建Django表单Forms继承BaseForm的问题
在创建Django表单时,因为需要验证用户输入的验证码是否正确,因此需要在session里提取当前验证码的值和POST提交过来的值进行比对,如图:
1 form.py
2
3 from django import forms
4
5 class LoginForms(forms.Form):
6 check_code = forms.CharField()
7 def clean_check_code(self):
8 if self.request.session.get('CheckCode').upper() !=
9 self.request.POST.get('check_code').upper():
10 raise ValidationError(message='验证码错误', code='invalid')
但是这样会引发一个AttributeError错误
因为这时的"self.request.session.get('CheckCode')"中的"self.request"指代的是表单forms(我猜的。。。),所以要继承个类BaseForm,然后在BaseForm写上
1 class BaseForm(object):
2 def __init__(self, request, *args, **kwargs):
3 self.request = request
4 super(BaseForm, self).__init__(*args, **kwargs)
原来代码改为:
1 class LoginForms(BaseForm,forms.Form):
2 check_code = forms.CharField()
3
4 def clean_check_code(self):
5 if self.request.session.get('CheckCode').upper() != self.request.POST.get('check_code').upper():
6 raise ValidationError(message='验证码错误', code='invalid')
然后在view.py上改为:
1 from ..forms.account import LoginForms
2
3 def login(request):
4 if request.method == 'GET':
5 return render(request, 'login.html')
6
7 elif request.method == 'POST':
8 obj = LoginForms(request = request, data = request.POST)
9 ....
Django Forms的错误提示
首先,在构建form表单时,可以用"error_messages={}"自定义错误信息,例如:
# form.py 1 from django import forms
2
3 # Create your models here.
4 class TestForm1(forms.Form):
5 username = forms.CharField(
6 min_length=3,
7 max_length=20,
8 error_messages={'required':'用户名不能为空', # 用户名为空时显示的错误信息
10 'min_length':'大于3个字符',
11 'max_length':'小于20个字符'}
12 )
view.py
1 def login(request):
2 if request.method == 'GET':
3 return render(request, 'login.html')
4
5 elif request.method == 'POST':
8 obj = LoginForms(request.POST)
9 if obj.is_valid():
10 pass
11 else:
12 error = obj.errors
13 return render(request, 'login.html', {'errors': error}) # 以字典的形式发送到前端显示出来
HTML上相对应的部分代码显示
1 <div class="form-group">
2 <label for="username">用户名</label>
3 <input type="text" class="form-control" name="username" id="username" placeholder="请输入用户名">
4 <p style="color: red;">{{ errors.username.0 }}</p> {#“.0”的作用是在界面显示错误信息时,可以去掉前面的点"·"#}
5</div>
2、invalid
我们知道,在定制表单信息的格式时,除了可以自定义外,Django内部给我们备好的,例如可以用Django内部帮我们定义好的slug
1 from django.core import validators
2 class TextForm(forms.Form):
3 # slug指有效URL的一部分,能使URL更加清晰易懂
4 slug = forms.CharField(validators=[validators.validate_slug],
6 error_messages={'required':'不能为空'7 })
如果格式错误,则错误信息显示为"Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."
那么如何自定义错误信息,让它在格式错误时显示我们自己定义的内容呢?通过查看该函数源码,我们可以发现内定的格式错误信息是由"invalid"这个参数规定的
1 # https://docs.djangoproject.com/zh-hans/2.1/_modules/django/core/validators/#RegexValidator
2 slug_re = _lazy_re_compile(r'^[-a-zA-Z0-9_]+\Z')
3 validate_slug = RegexValidator(
4 slug_re,
5 # Translators: "letters" means latin letters: a-z and A-Z.
6 _("Enter a valid 'slug' consisting of letters, numbers, underscores or hyphens."),
7 'invalid'
8 )
那么,我们就可以在"error_messages={}"里写上
1 slug = forms.CharField(validators=[validators.validate_slug],
2
3 error_messages={'required':'不能为空',
4 'invalid':'输入由字母,数字,下划线或连字符组成的有效“slug”'})
这样,当格式错误时,显示的错误信息就是我们自己自定义的
3、ValidationError
当填入在表单上的信息全部符合格式,但是还可能存在一种错误情况,例如注册用户时,用户名重复的情况,这时格式通过表单的信息验证,但是却不符合我们自己对于注册格式的定义时,怎么把错误信息显示出来??这就要用到“ValidationError”
1 form.py
2
3 from django.core.exceptions import ValidationError
4
5 class LoginForms(BaseForm, forms.Form):
6 username = forms.CharField()
7 # 钩子函数
8 def clean_username(self):
9 username = self.request.POST.get('username')
10 is_exsit = models.UserInfo.objects.filter(username=username) # 去数据库查找用户名是否已存在
11 if is_exsit:
12 raise ValidationError(message='用户名已存在', code='invalid')
django下载安装cmd创建项目
官网地址:https://www.djangoproject.com/download/
1.下载django
pip3 install django==1.11.9
2.创建一个django project
django—admin startproject mysite #创建一个名为‘mysite’的django项目
3、创建的项目文件介绍
manage.py ----- Django项目里面的工具,通过它可以调用django shell和数据库,启动关闭项目与项目交互 等,不管你将框架分了几个文件,必然有一个启动文件,其实他们本身就是一个文件。
settings.py ---- 包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量。
urls.py ----- 负责把URL模式映射到应用程序。
wsgi.py ---- runserver命令就使用wsgiref模块做简单的web server,后面会看到renserver命令,所有与 socket相关的内容都在这个文件里面了,目前不需要关注它。
4.运行项目
python manage.py runserver 127.0.0.1:8080 #此时已经可以启动django项目了,只不过什么逻辑也没有呢
通过指令运行项目的时候,可以不写ip地址,如果不写,默认是127.0.0.1
python manage.py runserver 8080
如果连端口都不写,默认是8000端口
python manage.py runserver
pycharm创建项目
1.New Project
2.选择django,选择Existing interpreter
3.more settings里的Application name写app名字
写项目全流程
1 创建项目 first_pro
2 创建app app01 做一个登录页面的web项目,浏览器输入一个网址得到一个web页面
用户: http:127.0.0.1:8001/login/
1 urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index),
url(r'^book/', views.book_start),
]
2 写逻辑视图
from django.shortcuts import render,HttpResponse
# Create your views here.
def index(request):
print(request.method) #'POST' 'GET'
if request.method == 'GET':
return render(request,'model1.html')
else:
# print(request.GET)
print(request.POST)
username = request.POST.get('username')
password = request.POST.get('password')
if username == 'dazhuang' and password == '123':
return HttpResponse('登录成功!')
else:
return HttpResponse('登录失败!')
def book_start(request):
return render(request,'book.html')
3 创建html文件
在templates文件夹中创建一个login.html文件
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bootstrap 101 Template</title>
</head>
<body>
<h1>欢迎来到登录界面</h1>
<form action="/index/" method="post">
用户名:<input type="text" name="username">
密码:<input type="text" name="password">
<input type="submit">
</form>
</body>
</html>
urls.py里面需要注意的问题 url(r'^index/', views.index), 第一个参数:路径正则字符串 第二个参数:对应的视图函数
views.py
def login(request):
获取请求方法,request.method--'GET','POST'
request.GET -- GET请求发送来的所有数据,queryDict类型
request.POST -- POST请求发送来的所有数据,queryDict类型
<input name='username'>
request.POST.get('username')
request.GET.get('username')
return HttpResponse('字符串') --- 回复字符串数据
retuen render(request,'login.html')
get请求获取数据
print(request.GET) #<QueryDict: {'username': ['dazhuang'], 'password': ['123']}>
post请求提交数据时关掉一个认证机制,settings配置文件中
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
url配置
无名分组
url(r'^books/(\d{4})/', views.books),
位置参数
url(r'^books/(\d{4})/', views.year_books), #匹配年份的
url(r'^books/(\d{4})/(\d{1,2})/', views.year_month_books), #匹配年份和月份的
# http://127.0.0.1:8000/books/2001/
视图:
def year_month_books(request,year,month): #位置参数,第一个参数接收的就是无名分组路径中匹配 到的第一个分组的数据,第二个参数接收的就是无名分组路径中匹配到的第二个分组的数据 print(year,month)
# return render(request,'books.html')
return HttpResponse(year+month)
有名分组
url(r'^books/(?P<year>\d{4})/(?P<month>\d{1,2})/', views.year_month_books), #匹配年份和月 份的
def year_month_books(request,month,year): #形参名称要和url中的分组名对应好,参数位置就没有要求 了 print(year,month)
# return render(request,'books.html')
return HttpResponse(year+month)
默认值
# urls.py中 from django.conf.urls import url
from . import views
urlpatterns = [ url(r'^blog/$', views.page),
url(r'^blog/page(?P<num>[0-9]+)/$', views.page), ]
# views.py中,可以为num指定默认值 def page(request, num="1"): pass