MVC
Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,结构说明如下:
M: models 数据库相关操作
V: views 视图,也就是业务逻辑相关操作
C: controller 控制器,也就是通过路径找到对应视图函数
MTV
Django的MTV模式本质上和MVC是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同。
M: models 数据库相关操作
T: templates html文件相关操作(模板渲染等)
V: views 视图,也就是业务逻辑相关操作
加上一个url控制器,也就是通过路径找到对应视图函数
WSGI(Web Server Gateway Interface)就是一种规范,它定义了web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。
开发的项目分为两个部分的程序
1 服务器程序 socket相关功能的模块,wsgiref、uwsgi等等,负责接收网络请求并解析网络请求相关数据,分装加工成一些可用的数据格式,格式大家都是统一的,方便应用程序的开发
2 应用程序 就是使用接收到的网络请求相关数据,进行逻辑代码的开发,比如数据库相关操作,数据结构的调整、文件操作等等。。。
Django是MTV模式架构的框架。历史版本介绍,参考:https://www.djangoproject.com/download/
pip3 install django==1.11.9
可以通过指令来创建,将来还可以通过IDE来进行创建,先看指令的写法
django-admin startproject mysite #mysite是自己的项目名称
执行上面的指令之后会生成如下的目录,当前目录下会生成mysite的工程,里面有个主目录和我们创建的项目目录同名,在项目目录下有个manage.py文件,在主目录下有settings.py\urls.py\wsgi.py,每个文件的功能介绍如下:
manage.py ----- Django项目里面的工具,通过它可以调用django shell和数据库,启动关闭项目与项目交互等,不管你将框架分了几个文件,必然有一个启动文件,其实他们本身就是一个文件。
settings.py ---- 包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量。
urls.py ----- 负责把URL模式映射到应用程序。
wsgi.py ---- runserver命令就使用wsgiref模块做简单的web server,后面会看到renserver命令,所有与socket相关的内容都在这个文件里面了,目前不需要关注它。
在一个项目中可以有多个应用,每个应用完成项目的部分功能,比如微信项目,其中有很多的应用,比如朋友圈功能、聊天、支付等等。在django框架看来,每个应用都有自己的逻辑部分,数据库部分等等,所以我们进行业务逻辑开发的时候,需要创建应用来写逻辑。
指令如下
python manage.py runserver 127.0.0.1:8080
ip和port可以不用写,不写的话,默认是 127.0.0.1:8000
运行起来之后,通过浏览器访问127.0.0.1:8080,就能看到django给我们提供的一个初始页面。
创建应用的指令如下:需要借助到我们创建的项目中的manage.py文件
python manage.py startapp blog #blog是应用名称
执行完指令之后,生成如下目录:
目录介绍
models.py 数据库相关内容
views.py 视图,业务逻辑代码相关内容
tests.py 用来写一些测试代码,用来测试我们自己写的视图的,目前用不到
..其他的以后再说
在pycharm中新建一个项目
点击create按钮,项目就创建好了
看到如下提示
July 10, 2020 - 01:19:03
Django version 1.11.9, using settings 'django01.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
访问http://127.0.0.1:8000/,就能看到django页面了。
在应用文件夹下面的views.py文件中写视图函数,比如:
def home(request):
#request--environ 请求相关数据,request叫做HTTPRequest对象,请求相关数据都是request的属性
return render(request, 'home.html')
#render方法用来打开html文件,对文件进行模板渲染,第一个参数是request,第二个参数是html文件路径,(这里我只写了文件名称,因为django会自动去templates文件夹下面去找html文件),渲染完整之后,返回响应页面数据,最终交给wsgi中的socket将页面数据返回给客户端
如果视图响应的是一个html文件,那么我们需要在项目目录下面的templates文件夹中创建一个htm文件,比如叫做home.html
为什么django能够自动找到templates文件夹下面的home.html文件呢?也就是我们使用render方法时,只写了一个home.html文件名称,就能够自动帮我们找到这个home.html文件,是因为在django的默认配置中有一个关于templates模板相关功能的配置项,里面指定了html文件的存放目录,找到主目录下面的settings.py,打开这个文件,找到TEMPLATES这个配置,内容如下
#BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 项目根路径
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')] #DIRS指的就是html文件存放路径的配置,注意,有些版本的django或者通过指令来创建项目时,这个DIRS的配置是空的,需要我们自己指定html文件的存放路径,所以如果你发现这个DIRS为空,我们需要自定来配置路径,可以按照现在这种方式来写
,
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
在项目主目录的urls.py文件中的urlpatterns中写上如下内容
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
# url(r'^admin/', admin.site.urls), #这是django提供的一个内置应用,暂时忽略它
# ('/home',views.home), 我们之前的写法
url(r'^home/', views.home),
]
url中的正则路径如何找到对应的函数的,看简单分析:
#伪代码
import re
path = request.path 当前请求路径
for item in urlpatterns:
ret = re.match(item.regex,path) #item.regex -- ^home/
if ret:
item.func(request) #item.func--views.home函数
break
else:
pass
简单介绍几个request对象的属性
在views.py文件的视图函数中打印以下几个属性,看看效果:
def home(request):
print(request) # wsgi封装的一个对象,WSGIRequest类的对象
print(request.path) #/home/ 请求路径
print(request.method) #GET 请求方法
print(request.POST) #获取post请求提交过来的数据
#
uname = request.POST.get('username') #取出username数据
return render(request, 'xx/home.html')
views.py内容如下
####一个视图函数搞定登录功能#####
def login(request):
if request.method == 'GET':
return render(request,'login.html')
else:
print(request.POST)
#
uname = request.POST.get('username')
pwd = request.POST.get('password')
if uname == 'root' and pwd == '123':
return render(request,'xx/home.html')
else:
return HttpResponse('用户名或者密码错误!!请重新登录')
urls.py文件内容如下
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^home/', views.home),
url(r'^login/', views.login),
# url(r'^login2/', views.login2),
]
Login.html内容如下
Title
{#
在django中,url中的路径写法是正则,正则里面有无名分组正则,有有名分组正则,那么对应django中的功能,我们称之为无名分组路由和有名分组路由
看写法,urls.py文件中内容如下
urlpatterns = [
...
url(r'^books/(\d+)/(\d+)/', views.book),
#正则里面()分组正则,会将分组中的正则匹配到的内容作为返回值返回
]
简单分析,伪代码
'''
当用户请求的路径是它: /books/2019/8/
url(r'^books/(\d+)/(\d+)/', views.book), 里面做的事情如下
re.match(^books/(\d+)/,/books/2019/)
2019 和 8 作为位置参数交给了要执行的book视图函数
视图函数book的写法
def book(request,xx,oo):
xx = 2019
oo = 8
pass
'''
#位置传参,url中正则^books/(\d+)/(\d+)/,那么第一个()分组匹配到的数据,作为book函数的第二个参数,第二个()分组匹配到的数据,作为book的第三个参数
def book(request, year, month):
print('year', year, 'month', month) #year 2020
# return HttpResponse('%s所有书籍' % year)
return HttpResponse('%s年%s月所有书籍' % (year, month))
1. urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
2. 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(正则分组匹配)。
3. 不需要添加一个前导的反斜杠(也就是写在正则最前面的那个/),因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
4. 每个正则表达式前面的'r' 是可选的但是建议加上。
5. ^articles$ 以什么结尾,以什么开头,严格限制路径
其实就玩得正则的有名分组,看示例
Urls.py文件中的写法
urlpatterns = [
url(r'^admin/', admin.site.urls),
# /books/2020/6/
url(r'^books/(?P\d+)/(?P\d+)/', views.book),
# {'year':2020,'month':6},url类将有名分组正则匹配出来的数据,交给了book视图函数作为关键字参数来使用
]
# ^books/(?P\d+)/(?P\d+)/
#获取到url中的有名分组正则匹配到的数据,那么函数形参名称必须和有名分组正则的那个名称相同才可以,也就是按照上面的url来看的话,函数的形参必须是year和month这两个名称,并且关键字传参不需要考虑函数形参的位置
def book(request, month, year):
# print('year', year, 'month', month) #year 2020
print(request.path) #/books/2020/6/
# return HttpResponse('%s所有书籍' % year)
return HttpResponse('%s年%s月所有书籍' % (year, month))
当用户通过浏览器访问django框架完整的项目中的某个路径时,如果用户在输入网址路径的最后,没有加上/斜杠,比如http://127.0.0.1:8000/test,那么django会发将用户输入的网址路径加上一个后置的/,也就会将路径变成这样http://127.0.0.1:8000/test/,然后给浏览器发送一个重定向的响应操作,状态码为301,那么浏览器拿到这个重定向操作之后,就会自动发起一个这样的路径请求http://127.0.0.1:8000/test/,所以当我们打开浏览器控制台的network功能查看请求过程时,会看到两个请求,一个没有后置的斜杠的,一个是有后置斜杠的。第二个请求才会走到我们的urs.py文件中的路径配合和分发对应视图的地方。
我们可以通过一个配置项,告诉django,不要自动加路径后面的斜杠了,但是需要注意的就是你自己写的url中的正则,也别加后面的斜杠,不然正则匹配不到。
配置项直接写在settings配置文件中,任意位置
APPEND_SLASH = False #False表示告诉Django,不加路径后面的斜杠,默认值是True
views.py文件:
# 在路由没有匹配任何参数的时候,num使用自己的默认值
# 当路由中有分组匹配数据的动作,比如url(r'^test/(\d+)/', views.test),用户输入网址:http://127.0.0.1:8000/test/22/,那么22就被匹配到了,会作为参数传给我们的test函数,那么num的值就变成了22
def test(request, num=10):
print('number>>>',num)
return HttpResponse('test')
urls.py文件
# url(r'^test/', views.test),
url(r'^test/(\d+)/', views.test),
常用属性和方法
print(request) #wsgirequest对象
print(request.path) #请求路径 #/index/
print(request.method) #请求方法
print(request.POST) #post请求提交的数据
print(request.GET) # 获取url中的查询参数 #不是针对get请求的
print(request.body) #获取http请求消息格式的请求数据部分的内容 b''
print(request.META) #请求头信息
print(request.get_full_path()) #获取完整路径(包含查询参数的) /index/?a=1&b=3
print(request.FILES) # 上传的文件对象数据
print(request.FILES.get('file')) #26机试.pdf
#
print(request.POST.get('username')) #zhen
print(request.POST.get('sex')) #female
# 多选提交来的数据通过getlist来获取
print(request.POST.getlist('hobby')) #['2', '3']
常用方法
from django.shortcuts import render, HttpResponse, redirect
return HttpResponse('你好') #回复字符串
return render(request,'home.html') #回复html页面
#重定向方法,参数是个路径
return redirect('/home/') #封装了302状态码,以及浏览器要重定向的路径
ret = render(request,'home.html')
ret['a'] = 'b' #添加响应头键值对
return ret
ret = render(request,'home.html', status=202) #render修改状态码还可以这样改
#ret['a'] = 'b' #添加响应头键值对
ret.status_code = 201 #添加响应状态码
return ret #回复html页面
两种视图逻辑的写法方法
FBV:全称function based view,就是基于函数来写视图逻辑
CBV:全称class based view,就是基于类来写视图
基于函数的写法,咱们写了很多了,这里不放示例了
基于类的视图写法,如下,views.py文件
from django.views import View
#登录需求
class LoginView(View):
# get请求 获取login页面
def get(self,request):
return render(request,'login.html')
# post请求,获取post请求提交的数据,并校验等等
def post(self,request):
print(request.POST)
#
return render(request,'login.html')
url路径的写法:urls.py文件
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^index/', views.index),
url(r'^home/', views.home),
# 类视图的url写法
url(r'^login/', views.LoginView.as_view()),
]
from django.views import View
View里面的dispatch方法中的反射逻辑,实现了不同的请求方法,找到我们视图类中的对应方法执行
FBV和普通函数加装饰器方式一样
示例:
#装饰器函数
def outer(f):
def inner(request, *args ,**kwargs):
print('前面')
ret = f(request, *args ,**kwargs)
print('后面')
return ret
return inner
#使用装饰器
@outer
def books(request):
print('FBV执行了')
return HttpResponse('book.html--ok')
#装饰器函数
def outer(f):
def inner(request, *args ,**kwargs):
print('前面')
ret = f(request, *args ,**kwargs)
print('后面')
return ret
return inner
#使用装饰器
#1 引入django提供的装饰器方法method_decorator来配合装饰器函数的使用
from django.utils.decorators import method_decorator
@method_decorator(outer,name='get') #CBV加装饰器方式3
class BookView(View):
#给类方法统一加装饰器,借助dispatch方法(父类的dispatch方法,就是通过反射来完成不同的请求方法找到并执行我们自己定义的视图类的对应方法)
# 重写dispatch方法,dispatch方法是在其他请求方法对应的类方法执行之前执行的
# @method_decorator(outer) #加装饰器方式2
def dispatch(self, request, *args, **kwargs):
# print('xxxxxx')
ret = super().dispatch(request, *args, **kwargs)
# print('oooooo')
return ret
#CBV加装饰器的方式1,给单独的方法加装饰器
# @method_decorator(outer)
def get(self, request, xx):
print('CBV的get方法')
return render(request, 'book.html')
# @method_decorator(outer)
def post(self,request, xx):
print('CBV的post方法')
return HttpResponse('ok')
模板渲染,模板指的就是html文件,渲染指的就是字符串替换,将模板中的特殊符号替换成相关数据
{
{ 变量 }}
{% 逻辑 %}
Views.py文件
def home(request):
class A:
def __init__(self):
self.username = '谢晨'
#如果方法需要在模板中使用,那么不能有其他参数
def xx(self):
return '谢晨xx'
info = {
'name': '王振',
'hobby': ['宝剑', '足疗', '洗头'],
'num': 100,
'd1':{'xx':'oo'},
'l1':[11,22,{'ss':'kk'}],
'a': A(),
}
return render(request, 'home.html', info)
home.html内容如下
重点:万能的据点号。通过点可以进行数据的索引取值,属性取值,取方法等等
Title
{
{ name }}
{% for i in hobby %}
- {
{ i }}
{% endfor %}
{
{ num }}
{
{ l1.2.ss }}
{
{ d1.xx }}
{
{ a.username }}
{
{ a.xx }}
urls.py内容
url(r'^home/', views.home),
在Django的模板语言中,通过使用 过滤器 来改变变量的显示。
过滤器的语法:{
{ value|filter_name:参数 }}
使用过滤器的注意事项
1. 过滤器支持“链式”操作。即一个过滤器的输出作为另一个过滤器的输入。
{
{ sss|过滤器1:30|过滤器2.... }}
2. 过滤器可以接受参数,例如:{
{ sss|truncatewords:30 }},这将显示sss的前30个词。
3. '|'左右没有空格
常用的内置过滤器示例
default
如果一个变量是false或者为空,使用给定的默认值。 否则,使用变量的值。
{
{ value|default:"nothing"}}
如果value没有传值或者值为空或者为False的话就显示nothing
length
返回值的长度,作用于字符串和列表。
{
{ value|length }}
返回value的长度,如 value=['a', 'b', 'c', 'd']的话,就显示4.
filesizeformat
将值格式化为一个 “人类可读的” 文件尺寸 (例如 '13 KB', '4.1 MB', '102 bytes', 等等)。例如:
{
{ value|filesizeformat }}
如果 value 是 123456789,输出将会是 117.7 MB。
slice
切片,如果 value="hello world",还有其他可切片的数据类型
{
{value|slice:"2:-1"}}
date
格式化,如果 value=datetime.datetime.now(),如果不想显示为UTC时间,将django的settings.py配置文件中的TIME_ZONE这一项修改一下,修改为TIME_ZONE = 'Asia/Shanghai'
比如,后台返回的数据为{'value':datetime.datetime.now()}
对上面的数据进行格式化:{
{ value|date:"Y-m-d H:i:s"}}
关于时间日期的可用的参数(除了Y,m,d等等)还有很多,有兴趣的可以 去查查看看。
safe
xxs攻击,全称跨站脚本攻击 Django的模板中在进行模板渲染的时候会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全,django担心这是用户添加的数据,比如如果有人给你评论的时候写了一段js代码,这个评论一提交,js代码就执行啦,这样你是不是可以搞一些坏事儿了,写个弹窗的死循环,那浏览器还能用吗,是不是会一直弹窗啊,这叫做xss攻击,所以浏览器不让你这么搞,给你转义了。但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。
{'a_tag':'百度',}
渲染
{
{ a_tag|safe }} #生成标签效果
truncatechars
如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾。
参数:截断的字符数
{
{ value|truncatechars:9}} #注意:最后那三个省略号也是9个字符里面的,也就是这个9截断出来的是6个字符+3个省略号,有人会说,怎么展开啊,配合前端的点击事件就行啦
truncatewords
在一定数量的字后截断字符串,是截多少个单词。
例如:‘hello girl hi baby yue ma’,
{
{ value|truncatewords:3}} #上面例子得到的结果是 'hello girl h1...'
cut
移除value中所有的与给出的变量相同的字符串
{
{ value|cut:' ' }}
如果value为'i love you',那么将输出'iloveyou'.
join
使用字符串连接列表,{
{ list|join:', ' }},就像Python的str.join(list)
{
{ hobby|join:'+' }}
注意:模版渲染在浏览器渲染之前,模板渲染就是字符串替换,替换完成之后,将替换完的整体文件字符串返回给浏览器,浏览器再进行浏览器渲染,展示页面效果
语法{% 标签 %}
示例:
#循环列表
{% for xx in hobby %}
- {
{ xx }}
{% empty %} #当hobby为空或者后台没有给这个数据,那么会显示empty下面的内容
抱歉,没有查询到相关数据
{% endfor %}
#循环字典
{% for k,v in d1.items %}
- {
{ k }}--{
{ v }}
{% endfor %}
#循环计数
{% for i in l1 %}
{# {
{ forloop }}#}
{% for h in hobby %}
- {
{ forloop.parentloop.counter }}--{
{ forloop.revcounter0 }}---{
{ h }}--{
{ forloop.last }}
{% endfor %}
{% endfor %}
forloop的解释
注:循环序号可以通过{{forloop}}显示,必须在循环内部用
forloop.counter 当前循环的索引值(从1开始),forloop是循环器,通过点来使用功能
forloop.counter0 当前循环的索引值(从0开始)
forloop.revcounter 当前循环的倒序索引值(从1开始)
forloop.revcounter0 当前循环的倒序索引值(从0开始)
forloop.first 当前循环是不是第一次循环(布尔值)
forloop.last 当前循环是不是最后一次循环(布尔值)
forloop.parentloop 本层循环的外层循环的对象,再通过上面的几个属性来显示外层循环的计数等
#反向循环
可以利用{% for obj in list reversed %}反向完成循环。
示例:
{% for xx in hobby reversed %}
- {
{ xx }}
{% endfor %}
{% if %}会对一个变量求值,如果它的值是“True”(存在、不为空、且不是boolean类型的false值),对应的内容块会输出。
{% if num > 100 or num < 0 %}
无效
{% elif num > 80 and num < 100 %}
优秀
{% else %}
凑活吧
{% endif %}
当然也可以只有if和else
{% if user_list|length > 5 %}
七座豪华SUV
{% else %}
黄包车
{% endif %}
if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断,注意条件两边都有空格。
使用一个简单地名字缓存一个复杂的变量,多用于给一个复杂的变量起别名,当你需要使用一个“昂贵的”方法(比如访问数据库)很多次的时候是非常有用的
例如:
注意等号左右不要加空格。
{% with total=business.employees.count %}
{
{ total }}
{% endwith %}
或
{% with business.employees.count as total %}
{
{ total }}
{% endwith %}
1. Django的模板语言不支持连续判断,即不支持以下写法:
{% if a > b > c %}
...
{% endif %}
1. Django的模板语言中属性的优先级大于方法(了解)
def xx(request):
d = {"a": 1, "b": 2, "c": 3, "items": "100"}
return render(request, "xx.html", {"data": d})
如上,我们在使用render方法渲染一个页面的时候,传的字典d有一个key是items并且还有默认的 d.items() 方法,此时在模板语言中:
{
{ data.items }}
默认会取d的items key的值。
自定义过滤器
注意:在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag.
1、在app中创建templatetags文件夹(文件夹名只能是templatetags)
2、在templatetags文件夹中创建任意 .py 文件,如:mytag.py
3、在mytag.py文件中写上如下内容
from django import template
register = template.Library() #制作注册器,名字必须叫register
#过滤最多两个参数
@register.filter #注册过滤器,需要两个参数的
def add(v1, v2): #v1表示管道符前面的,v2表示冒号后面的参数
print(v1,v2) #100 50
return v1 + v2
@register.filter #注册过滤器,需要一个参数的
def xxx(v1): #v1表示管道符前面的
print(v1)
return 'xxx'
4、使用,在html文件中写上如下内容
{% load mytag %}
Title
base页面
{
{ num|add:50}}
{
{ num|xxx }}
过程:
1、在app中创建templatetags文件夹(文件夹名只能是templatetags)
2、在templatetags文件夹中创建任意 .py 文件,如:mytag.py
3、在mytag.py文件中写上如下内容
from django import template
register = template.Library() #制作注册器,名字必须叫register
@register.simple_tag
def atag(v1,v2): #没有参数个数限制
print(v1,v2)
return v1 + v2
4、使用,在html文件中写上如下内容
{#{% load mytag %}#}
Title
base页面
{% load mytag %}
{% atag a b %}
将一些页面公共的部分,可以抽离出来单独做成一个html页面,使用这些公用部分的其他html文件,只需要继承一下它就可以了,具体使用流程如下:
1 创建公用模板,比如内容如下
Title
{% block content %}
公共页面
{% endblock %}
{% block js %}
{% endblock %}
2 将来如果说继承公用模板的html文件中需要修改公用模板中的一些内容,那么需要在公用模板中预留一些钩子,钩子的写法如下
{% block content %} #block 后面的块名称随便起
公共页面
{% endblock %}
#也可以这样写 {% endblock content %} #endblock指定名称
3 继承公用模板需要在html文件中写如下内容:
{% extends 'xx.html' %}
{% block css %}
.nav{
height: 60px;
background-color: pink;
}
{% endblock %}
{% block content %}
首页
{% endblock %}
4 在使用公用模板的其他html文件中,如果需要更改公用模板里面的内容,只需要在html文件中写上相同的钩子,钩子里面写上自定义的内容,写法如下
{% block css %}
.nav{
height: 60px;
background-color: pink;
}
{% endblock %}
{% block content %}
首页
{% endblock %}
- 如果你在模版中使用 {% extends %} 标签,它必须是模版中的第一个标签。其他的任何情况下,模版继承都将无法工作,模板渲染的时候django都不知道你在干啥。
- 在base模版中设置越多的 {% block %} 标签越好。请记住,子模版不必定义全部父模版中的blocks,所以,你可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。多一点钩子总比少一点好。
- 如果你发现你自己在大量的模版中复制内容,那可能意味着你应该把内容移动到父模版中的一个 {% block %} 中。
- {
{ block super }}的使用,在子模板中也展示出父模板原来钩子中的内容
{% block content %}
首页
{
{ block.super }}
{% endblock %}
- 为了更好的可读性,你也可以给你的 {% endblock %} 标签一个 名字 。例如:
{% block content %}
...
{% endblock content %}
在大型模版中,这个方法帮你清楚的看到哪一个 {% block %} 标签被关闭了。
- 不能在一个模版中定义多个相同名字的 block 标签。
#两个block都叫content,这种写法是不对的
{% block content %}
首页
{
{ block.super }}
{% endblock %}
{% block content %}
首页
{
{ block.super }}
{% endblock %}
组件
组件就是一个html文件,其中封装了一些特定功能,比如就是一个导航栏,或者就是一个左侧菜单,相当我们将一些特定功能封装成了一个组件,将来任何其他html文件中如果使用这部分功能,可以直接引入使用。
在django模板渲染系统中使用组件的步骤
第一步:创建组件的html文件,名字随便取,比如叫做zujian.html,比如内容如下,做一个顶部导航栏组件
zujian
第二步:使用组件,需要借助下面这个标签语法
{% include '组件文件名称.html' %}
示例:比如我们需要某个html文件中使用,比如show.html文件中使用,show.html文件内容如下:
Title
{# #}
这是show页面
{% include 'zujian.html' %}
动态组件的应用
注意:在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的标签
1、在app中创建templatetags文件夹(文件夹名只能是templatetags)
2、在templatetags文件夹中创建任意 .py 文件,如:mytag.py
3、在mytag.py文件中写上如下内容
from django import template
register = template.Library() #制作注册器,名字必须叫register
@register.inclusion_tag('zujian2.html')
def xiaolin(v1):
#v1 = [11,22,33]
return {'data': v1}
4、 使用inclusion_tag,比如在show2.html文件中使用:
Title
{% load mytag %}
{% xiaolin d %}
这是show2页面
5、需要后台给show2.html传递数据,比如views.py文件写法如下
def show2(request):
d = ['国产', '欧美', '日韩']
return render(request,'show2.html',{'d': d})
1 在项目根目录下创建一个文件夹,名称随便,比如叫做xxx,用来存放我们的静态文件(js\css\图片文件等等)
2 在settings.py配置文件中写上如下内容
STATIC_URL = '/abc/' #静态文件夹路径别名
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'xxxxx'),
]
3 使用静态文件配置路径
三种写法
1 #直接使用静态文件夹路径别名
2 在html文件中{% load static %}
使用:
示例:
{% load static %}
Title
{# #}
{# #}
{# #}
张恒说对!
{##}
注意:就是先读取HTML文件,那时候还是字符串,没有调用js代码,所以没法模板渲染,当读取完浏览器加载运行代码时候才发送调用js代码请求,这时候模板渲染那段时间早就过了,所以没成功渲染
首先在项目根目录下执行以下指令
python manage.py startapp app01
然后在settings.py配置文件中的INSTALLED_APPS这一项配置列表里面,加上我们的应用名称。
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 'app01.apps.App01Config', #全称
'app01', #简写
]
如果没有配置应用,那么在应用中写逻辑时,django框架提供的很多功能,是没办法调用的
凡是手动创建的应用,都需要在这里配置一下
object relational mapping,对象关系映射。主要完成的就是数据库操作。
- MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
- ORM是“对象-关系-映射”的简称。(Object Relational Mapping,简称ORM)(将来会学一个sqlalchemy,是和他很像的,但是django的orm没有独立出来让别人去使用,虽然功能比sqlalchemy更强大,但是别人用不了)
- 类对象--->sql--->pymysql--->mysql服务端--->磁盘,orm其实就是将类对象的语法翻译成sql语句的一个引擎,明白orm是什么了,剩下的就是怎么使用orm,怎么来写类对象关系语句。
总结
优点:
开发效率高,应用程序开发程序员对类对象关系,比对sql语句要熟练一些
迁移数据库时只需要修改配置,orm就能够帮我们将类对象关系翻译成对应数据库的sql语句
缺点:
效率相对低,因为orm有一个翻译动作,现将orm语句翻译成sql语句,再去数据库里面执行,比直接写的原生sql语句多了一步翻译动作,所以效率相对低一些
创建表
在应用文件夹下的models.py文件中写上如下内容
class Book(models.Model):
id = models.AutoField(primary_key=True) #自增、主键
title = models.CharField(max_length=64,null=True) # null=True可为空
state = models.BooleanField(default=True) #default=True默认值
pub_date = models.DateField()
price = models.DecimalField(max_digits=20,decimal_places=5)
publish = models.CharField(max_length=32)
对应的sql语句是这样的:
create table book(
id int primary key auto_increment,
title varchar(64),
state boolean not null default true,
pub_date date not null,
price decimal(20,5) not null,
publish varchar(32) not null
)
类写完之后,执行数据库同步(迁移)指令才能生成表
python manage.py makemigrations #在应用的migrations文件夹中生成迁移文件(也就是记录文件)
python manage.py migrate #执行对应的记录文件,翻译成sql语句并到数据库中执行对应的sql来创建表
可以通过pycharm提供的数据库图形化界面工具来查看表名以及表结构。
You are trying to add a non-nullable field 'state' to book without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
Select an option:
出现问题的原因就是,比如新增的state属性对应的字段,默认是不能为空的,如果我们没有设置它可以为空,或者没有给它设置默认值,那么如果数据表中有了几行数据,那么这几行中的新增的这里一列数据,就没办法自动添加数据,orm不知道给你添什么数据,所以遇到了上面的报错情况,上面的报错请求就是告诉你,给新添加的字段添加上默认值,或者设置可以为空才行。
解决办法
1 直接在提示信息的这个位置Select an option: 输入默认值,然后回车
2 退出窗口,在state = models.BooleanField(null=True) 或者state = models.BooleanField(default='默认值'),然后在执行数据库同步指令
第一步: 去mysql里面创建一个库
mysql> create database orm01 default charset=utf8mb4;
第二步:将settings.py配置文件中的DATABASES配置改成如下内容
#完整连接数据库的指令:mysql -h 127.0.0.1 -P 3306 -u root -p666
DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.sqlite3',
# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
# }
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'orm01',
'HOST':'127.0.0.1',
'PORT':3306,
'USER':'root',
'PASSWORD':'666'
}
}
#注意:字典中的每一项名称都是固定写法,并且必须都是大写
第三步:指定django连接mysql的python模块
1 下载pymysql
pip install pymysql
2 指定连接模块,需要在项目主目录下的__init__.py文件中来指定,内容如下
import pymysql
pymysql.install_as_MySQLdb()
第四步:执行数据库同步指令
python manage.py makemigrations
python manage.py migrate
比如:models.CharField(null=True,blank=True)
(1)null
如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False.
(1)blank
如果为True,该字段允许不填。默认为False。
要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。
如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。
(2)default
字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用,如果你的字段没有设置可以为空,那么将来如果我们后添加一个字段,这个字段就要给一个default值
(3)primary_key
如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True,
Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为,
否则没必要设置任何一个字段的primary_key=True。
(4)unique
如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的
(5)choices
由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,
而且这个选择框的选项就是choices 中的选项。
(6)db_index
如果db_index=True 则代表着为此字段设置数据库索引。
DatetimeField、DateField、TimeField这个三个时间字段,都可以设置如下属性。
(7)auto_now_add
配置auto_now_add=True,创建数据记录的时候会把当前时间添加到数据库。
(8)auto_now
配置上auto_now=True,每次更新数据记录的时候会更新该字段,标识这条记录最后一次的修改时间。
只能在save方式时触发自动更新时间的动作
可以在pip安装的django包中的这个位置查看/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/db/backends/mysql/base.py
'AutoField': 'integer AUTO_INCREMENT',
'BigAutoField': 'bigint AUTO_INCREMENT',
'BinaryField': 'longblob',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
'CommaSeparatedIntegerField': 'varchar(%(max_length)s)',
'DateField': 'date',
'DateTimeField': 'datetime',
'DecimalField': 'numeric(%(max_digits)s, %(decimal_places)s)',
'DurationField': 'bigint',
'FileField': 'varchar(%(max_length)s)',
'FilePathField': 'varchar(%(max_length)s)',
'FloatField': 'double precision',
'IntegerField': 'integer',
'BigIntegerField': 'bigint',
'IPAddressField': 'char(15)',
'GenericIPAddressField': 'char(39)',
'NullBooleanField': 'bool',
'OneToOneField': 'integer',
'PositiveIntegerField': 'integer UNSIGNED',
'PositiveSmallIntegerField': 'smallint UNSIGNED',
'SlugField': 'varchar(%(max_length)s)',
'SmallIntegerField': 'smallint',
'TextField': 'longtext',
'TimeField': 'time',
'UUIDField': 'char(32)',
添加单条数据
def add_book(request):
import datetime
# 添加数据
# 方式1
book_obj = models.Book(
title='少年阿宾',
state=True,
#日期时间类型数据,通过字符串或者日期时间类型数据作为参数数据,都是可以的
pub_date=datetime.datetime.now(),
# pub_date='2018-11-11',
price=11.11,
publish='刘伟出版社'
)
print(book_obj.title)
print(book_obj.publish)
book_obj.save() #保存数据
#方式2
book_obj = models.Book.objects.create(
title='金瓶梅',
state=True,
pub_date=datetime.datetime.now(),
# pub_date='2018-11-11',
price=1.5,
publish='武大郎出版社'
)
#重点记录:create方法会返回新添加的这条记录的类对象,通过这个对象.属性的方式能够获取到对应字段的数据
print(book_obj) # Book object
print(book_obj.title) # 金瓶梅
print(book_obj.publish) # 武大郎出版社
return HttpResponse('ok')
看示例:bulk_create
obj_list = []
for i in range(1,10):
obj = models.Book(
title=f'水浒传{i}',
state=True,
pub_date=f'2018-11-{i}',
price=11.11 + i,
publish='腾讯出版社'
)
obj_list.append(obj)
models.Book.objects.bulk_create(obj_list)
Update_or_create()
看示例
#有就更新,没有就添加update_or_create()
# 有就更新的操作示例:
# models.Book.objects.update_or_create(
# id=6,
# defaults={
# 'title':'西游记',
# 'state':True,
# 'pub_date':'2018-11-11',
# 'price':1.5,
# 'publish':'武大郎出版社'
# }
# )
# 没有就创建(添加)的示例:
# models.Book.objects.update_or_create(
# id=100,
# defaults={
# 'title': '红楼梦',
# 'state': True,
# 'pub_date': '2018-11-11',
# 'price': 1.5,
# 'publish': '武大郎出版社'
# }
# )
# 查询为多条记录的演示示例:报错,因为update_or_create内部进行查询时,使用的是get方法查询
# models.Book.objects.update_or_create(
# publish='武大郎出版社',
# defaults={
# 'title': '红楼梦2',
# 'state': True,
# 'pub_date': '2018-11-11',
# 'price': 1.5,
# 'publish': '武大郎出版社'
# }
# )
get_or_create()
# 查询或创建,能够查询到就返回查询结果,查询不到就添加记录,查询时内部使用的还是get方法
# 查询到结果的示例:
ret = models.Book.objects.get_or_create(
id=100,
defaults={
'title': '西游记',
'state': True,
'pub_date': '2018-11-11',
'price': 1.5,
'publish': '武大郎出版社'
}
)
print(ret) #(, False) #如果没创建,那么返回结果元祖中的第二个元素为Flase
#查询不到,自动添加记录的示例
ret = models.Book.objects.get_or_create(
id=102,
defaults={
'title': '西游记',
'state': True,
'pub_date': '2018-11-11',
'price': 1.5,
'publish': '武大郎出版社'
}
)
print(ret) #(, True) #如果创建了新纪录,那么返回结果元祖中的第二个元素为True
几个简单查询方法
all()查询所有的数据,返回结果为QuerySet类型数据,QuerySet类似于列表,里面存放的是model类的实例化对象,每个对象表示一条记录,对象中的对应数据有着该行记录的字段数据
# obj_list = models.Book.objects.all()
#QuerySet类似于列表,但是比列表还多一些其他的功能,这是orm给咱们封装出来的新的数据类型
#, ]>
# print(obj_list[0]) # 索引取值
# print(obj_list[0:3]) # 切片取值
filter() 过滤查询 ,结果也是queryset类型数据,里面的每一项也是模型类对象
# obj_list = models.Book.objects.filter(id=1)
# print(obj_list) # ]>
# obj_list = models.Book.objects.filter(publish='腾讯出版社')
# 当查不到任何内容时,返回空的queryset
# obj_list = models.Book.objects.filter(id=100)
# print(obj_list) #
# print(obj_list) # ]>
get() 过滤查询,但是结果有且只能有一条,结果不是queryset类型数据,而是一个模型类对象
# obj = models.Book.objects.get(id=1)
# print(obj)
# print(obj.price) # 11.11
# print(obj.publish) # 刘伟出版社
#错误示例1
# obj = models.Book.objects.get(publish='腾讯出版社')
# print(obj)
#查询结果不止一个,多于一个了,就报这个错误
#get() returned more than one Book -- it returned 9!
# 错误示例2
# obj = models.Book.objects.get(id=100)
# print(obj)
# 查询不到任何数据时,也会报错,错误如下
# Book matching query does not exist.
看示例:
修改
方式1 模型类对象修改数据
obj = models.Book.objects.get(id=1)
# print(obj)
obj.title = '少女白洁'
obj.price = 20
obj.save()
布尔值类型保存数据时,数据库中保存的是数字:1--True 0--False
方式2
update方法,调用者可以是objects控制器,可以是queryset类型数据,但是不能是模型类对象
objects调用,下面示例是整列数据更新
models.Book.objects.update(
state=False,
title='xx',
)
queryset类型数据调用,下面也是更新整理数据
models.Book.objects.all().update(
state=True,
# title='xx',
)
queryset类型数据调用,更新部分记录数据
# obj = models.Book.objects.filter(publish='刘伟出版社').update(
# # state=True,
# title='少年阿宾第1部',
# )
# print(obj) # 2 返回结果为受影响的行数
模型类对象不能直接调用update方法,错误示例如下
models.Book.objects.get(id=1).update(
title='xx',
)
报错信息'Book' object has no attribute 'update'
看示例
删除delete方法
# 调用者可以是model对象,可以是querset类型数据
# obj = models.Book.objects.get(id=3).delete()
# print(obj) # (1, {'app01.Book': 1})
# 返回结果其实还是受影响的行数
# obj = models.Book.objects.filter(publish='刘伟出版社').delete()
# print(obj) # (2, {'app01.Book': 2})
# 错误示例演示
# obj = models.Book.objects.delete()
# 报错'Manager' object has no attribute 'delete'
# 控制器没有delete方法,原因就是怕你一下子全部删除了所有数据
# 如果想删除所有数据,
看示例:
<1> all(): 查询所有结果,结果是queryset类型
<2> filter(**kwargs): 它包含了与所给筛选条件相匹配的对象,结果也是queryset类型,可以被queryset类型数据调用,也可以直接通过objects控制器进行调用:
看示例
models.Book.objects.filter(id=5) #filter(id!=5) 不能直接写不等于,想做不等于排除查询使用下面的exclude方法
models.Book.objects.all().filter(id=5)
Book.objects.filter(title='linux',price=100) #里面的多个条件用逗号分开,并且这几个条件必须都成立,是and的关系,or关系的我们后面再学,直接在这里写是搞不定or的
<3> get(**kwargs): 返回与所给筛选条件相匹配的对象,结果不是queryset类型,是行记录(模型类对象)对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。捕获异常try,get方法调用者可以是queryset类型数据,也可以是objects控制器。
示例:
Book.objects.get(id=1)
models.Book.objects.all().get(id=8)
<4> exclude(**kwargs): 排除的意思,它包含了与所给筛选条件不匹配的对象,没有不等于的操作昂,用这个exclude,返回值是queryset类型 ,调用者可以是queryset类型数据,也可以是objects控制器。
看示例:
Book.objects.exclude(id=6),返回id不等于6的所有的对象,
或者在queryset基础上调用,Book.objects.all().exclude(id=6)
<5> order_by(*field): 对查询结果排序, 返回值还是queryset类型,调用者可以是queryset类型数据,也可以是objects控制器。
示例:
models.Book.objects.order_by('price') #获取所有数据,并且按照price字段升序排列
models.Book.objects.order_by('-price') #获取所有数据,并且按照price字段降序排列
models.Book.objects.all().order_by('-price') #queryset类型数据调用
多条排序示例:
models.Book.objects.all().order_by('price','id') #直接写price,默认是按照price升序排列,按照字段降序排列,就写个负号就行了order_by('-price'),order_by('price','id')是多条件排序,按照price进行升序,price相同的数据,按照id进行升序
<6> reverse(): 可以是queryset类型的数据来调用,也可以是objects控制器调用,对查询结果反向排序,返回值还是queryset类型
看示例:
obj_list = models.Book.objects.all().order_by('id').reverse()
obj_list = models.Book.objects.reverse() # 注意:如果每给Book模型类执行Meta中的ordering属性,那么reverse()默认是不生效的。具体看西面的关于排序章节
<7> count(): queryset类型的数据来调用,也可以是objects控制器调用,返回数据库中匹配查询(QuerySet)的对象数量。返回结果是个数字。
看示例:
obj_list = models.Book.objects.count() #默认统计的整表的所有数据量
obj_list = models.Book.objects.all().count()
<8> first(): queryset类型的数据来调用,也可以是objects控制器调用,返回第一条记录对象,结果得到的都是model对象,不是queryset
示例:
Book.objects.all().first() #同:Book.objects.all()[0]
Book.objects.first()
<9> last(): queryset类型的数据来调用,也可以是objects控制器调用,返回最后一条记录对象,结果得到的都是model对象,不是queryset
示例:
Book.objects.all().last() #同:Book.objects.all()[-1] ,但是负数索引取值会报错,错误信息为: Negative indexing is not supported. queryset类型数据,不支持负数索引取值的形式
Book.objects.last()
<10> exists(): queryset类型的数据来调用,也可以是objects控制器调用,如果QuerySet包含数据,就返回True,否则返回False
看示例:
obj_list = models.Book.objects.exists() #判断表中是否有数据
obj_list = models.Book.objects.filter(id=100).exists() #判断查询结果集中是否有数据,有得到True,没有得到False
还有个点需要注意一下:
空的queryset类型数据布尔值为False,但是一般不用它来判断数据库里面是不是有数据,如果有大量的数据,你用它来判断,那么就需要查询出所有的数据,效率太差了,用count或者exits
看示例:
#'select * from book where name="xx";'
# if obj_list: 会将满足条件的所有数据进行一次查询,效率低
#select count(id) from book where name='xx';
# if obj_list.count(): 效率较高,按照查询结果对象的id值进行个数统计,
#select id from book where name='xx' limit 1; 查询一条数据,不用扫描所有数据
# if obj_list.exists(): #效率高
例:all_books = models.Book.objects.all().exists() #翻译成的sql是SELECT (1) AS `a` FROM `app01_book` LIMIT 1,就是通过limit 1,取一条来看看是不是有数据
<11> values(*field): 用的比较多,queryset类型的数据来调用,也可以是objects调用,返回一个特殊的QuerySet,运行后得到的并不是一系列model的实例化对象,返回的queryset类型,里面的元素是字典数据,既然是queryset类型数据,那么就可以继续链式调用queryset类型的其他的查找方法,其他方法也是一样的。
示例:
# obj_list = models.Book.objects.values() #默认获取的表中所有记录的字典数据,字典中的键是字段名称(模型类属性名称),值是每个字段的对应数据
# obj_list = models.Book.objects.all().values()
# 取指定字段数据
obj_list = models.Book.objects.all().values('id','title')
#
<12> values_list(*field): 它与values()非常相似,它返回的是一个包含元组queryset序列,values返回的是一个包含字典queryset序列
示例:
obj_list = models.Book.objects.all().values_list('id','title')
结果:#
#不太好的一点,就是数据不知道对应的是哪个字段
<13> distinct(): 去重, values和values_list得到的queryset类型的数据来调用,从返回结果中剔除重复纪录
示例:待核实
# obj_list = models.Book.objects.distinct()
# models.Book.objects.all().distinct()和models.Book.objects.distinct()
# 上面这两种写法都是是按照整条记录数据是否重复来进行去重,所以没有效果,因为最起码id数据是主键,不能重复,所以distinct方法不适合用在查询结果集中元素为模型类对象的这种结果集来去重
# 所以一般都是用在values和values_list方法后面,因为他们两个可以或取出指定字段数据,然后再去重
obj_list = models.Book.objects.all().values('publish').distinct()
print(obj_list)
关键字传参
两种方式:
方式1
filter(id=5, publish='腾讯出版社')
create(id=5, publish='腾讯出版社')
...
方式2
filter(**{'id':5, 'publish':'腾讯出版社'})
...
看示例:
方式1:
order_by(*field)方法进行排序
方式: 在模型类中通过Meta类来执行排序规则
class Book(models.Model):
id = models.AutoField(primary_key=True) #自增、主键
title = models.CharField(max_length=64,null=True) #
state = models.BooleanField(default=True)
pub_date = models.DateField(null=True)
# price = models.DecimalField(max_digits=20,decimal_places=5,null=True)
price = models.DecimalField(max_digits=8,decimal_places=2,null=True)
publish = models.CharField(max_length=32)
def __str__(self):
return self.title + '价格' + str(self.price)
class Meta:
ordering = ['id',] #制定了它之后,所有的本表的查询结果,都按照id进行升序排列,还可进行多天剑排序规则的指定
reverse()翻转,必须在上面两者的基础上,才能进行结果顺序翻转
python manage.py shell
执行上面这个指令会进入到python解释器环境中,并且加载了我们当前的django项目配置环境,所以,可以在当前shell中使用django内部的功能
Book.objects.filter(price__in=[100,200,300]) #price值等于这三个里面的任意一个的对象
示例:
ret = models.Book.objects.filter(price__in=['20.11', '1.5']) #针对decimal字段要用字符串
ret = models.Book.objects.filter(price2__in=[20, 18]) #针对float或者int类型要用数字
Book.objects.filter(price__gt=100) #大于,大于等于是price__gte=100,别写price>100,这种参数不支持
Book.objects.filter(price__lt=100) #小于,小于等于是price__lte=100
Book.objects.filter(price__range=[100,200]) #sql的between and,大于等于100,小于等于200
#针对一些字符串数据操作:
# 找到包含某些字符串的数据
Book.objects.filter(title__contains="python") #title值中包含python的
Book.objects.filter(title__icontains="python") #不区分大小写
# 找到以某些字符串开头或者结尾的数据
Book.objects.filter(title__startswith="py") #以什么开头,istartswith 不区分大小写
Book.objects.filter(title__istartswith='p')
Book.objects.filter(title__endswith="py") #以什么结尾,iendswith 不区分大小写
Book.objects.filter(title__iendswith='p')
# 日期时间类型数据的操作
# 按照年份查
Book.objects.filter(pub_date__year=2012)
Book.objects.filter(pub_date__year='2012')
# 查某年某月的
models.Book.objects.filter(pub_date__year=2018,pub_date__month=12)
# 查某年某月某日
models.Book.objects.filter(pub_date__year=2018,pub_date__month=11,pub_date__day=7)
# 只查某月
models.Book.objects.filter(pub_date__month=7)
# 查询某个字段为空的数据
models.Book.objects.filter(title__isnull=True) #正规的
models.Book.objects.filter(title=None)
# 原生sql语句:
#SELECT `app01_book`.`id`, `app01_book`.`title`, `app01_book`.`state`, `app01_book`.`pub_date`, `app01_book`.`price`, `app01_book`.`price2`, `app01_book`.`publish`, `app01_book`.`sex` FROM `app01_book` WHERE `app01_book`.`title` IS NULL ORDER BY `app01_book`.`id` ASC, `app01_book`.`publish` ASC
print(models.Book.objects.filter(title__isnull=True).query)
class Book(models.Model):
id = models.AutoField(primary_key=True) #自增、主键
...
publish = models.CharField(max_length=32)
def __str__(self):
return self.title + '价格' + str(self.price)
class Meta:
ordering = ['id','publish',]
obj_list = models.Book.objects.all().order_by('publish').values('publish').distinct()
#当模型类中制定了默认排序字段,那么当我们使用distinct方法进行去重时,默认会按照我们指定的排序字典进行去重,会导致去重结果不是我们想要的,所以要么我们在模型类中不指定排序字段,如果制定了排序字段,我们在使用distinct方法前面加上order_by方法,并且order_by方法中的字段就是我们要去重的字段数据
官方文档中的下面这种写法
Entry.objects.order_by('pub_date').distinct('pub_date')
只适用于PostgreSQL数据库,mysql不支持distinct方法里面写字段
使用
class Book(models.Model):
...
# sex = models.CharField(max_length=12)
sex_choices = ((1, '男性'),(0, '女性')) #enum枚举 单选
sex = models.IntegerField(choices=sex_choices, default=1)
ret = models.Book.objects.get(id=5)
print(ret.sex) # 1 获取到的是数据库中存储的字段数据
print(ret.get_sex_display()) # 男性 -- 能够帮我们获取到该字段数据对应的choices指定的元祖中的这个数据对应的文本内容
# sex_choices = ((1, '男性'), (0, '女性')) # enum枚举 单选
# sex = models.IntegerField(choices=sex_choices, default=1)
# 比如拿sex这个字段来说,数据库中存的数据是1,表示男性,如果我想获取到男性这个字符串数据,我直接通过模型类对象.get_sex_display()这个方法就能获取到,这个方法的语法是get_模型类属性名称_display()
使用
class ShowTime(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
brithday = models.DateTimeField(auto_now_add=True) #添加记录时,自动添加创建时间
bday = models.DateTimeField(auto_now=True) #添加记录时,也能自动添加创建时间,并且更新记录时,自动更新为当前时间
操作
#models.DateTimeField(auto_now=True)中的auto_now=True对update方法没有效果
# models.ShowTime.objects.all().update(
# name='chao2',
# )
#auto_now=True只有在使用save方法来进行更新动作时,才生效,才会自动更新修改时间
ret = models.ShowTime.objects.get(id=1)
ret.name = 'zhen'
ret.save()
由于将来项目中的不同功能对应的url路径可能会发生变化,所以我们在每个url路径上加上一个别名,将来通过别名反向解析来使用这个别名对应的路径,那么不管路径将来发生什么变化,只要别名不变,那么逻辑中使用这个路径的地方,都可以通过别名获取到
urlpatterns = [
...
url(r'^add_book/', views.add_book, name='add_book'), #name属性对应的值,就是这个路径的别名
]
看示例:
首先在视图中引入反向解析的方法
from django.urls import reverse #url别名反向解析,通过name别名对应的数据,解析出我们的url路径
1 针对没有参数的别名 url(r'^add_book/', views.add_book, name='add_book'),
反向解析:reverse('book_list')
2 针对含有无名分组的url:url(r'^book_list/v2/(\d+)/(\d+)/', views.book_list, name='book_list'),
反向解析:reverse('book_list',args=(11,22))
3 针对含有有名分组的url:url(r'^book_list/v2/(?P\d+)/(?P\d+)/', views.book_list, name='book_list'),
反向解析:reverse('book_list',kwargs={'year':2022,'month':11})
看示例
针对无参数的路径:url(r'^add_book/', views.add_book, name='add_book'),
反向解析:添加书籍
这对有参数的路径:url(r'^add_book/(\d+)/(\d+)/', views.add_book, name='add_book'),
url(r'^add_book/(?P\d+)/(?P\d+)/', views.add_book, name='add_book'),
反向解析: 添加书籍
完成新项目的前提事情:需求分析和表结构设计(就是我们下面的表关系)
表关系介绍
ER图,实体关系图 navicat和powdesigner工具可以学一下
from django.db import models
# Create your models here.
class Author(models.Model):
"""作者表"""
# id = models.AutoField(primary_key=True)
# 其实模型类中的id主键字段不需要我们手动指定,django的orm默认会给每张表都添加一个id字段并且为主键,如果我们自己指定了主键,以我们自己指定的为准,就不会自动帮你添加主键字段了
name = models.CharField(max_length=32)
age = models.IntegerField()
# ad = models.ForeignKey(to='AuthorDetail',to_field='id',on_delete=models.CASCADE)
ad = models.ForeignKey('AuthorDetail', on_delete=models.CASCADE)
# books = models.ManyToManyField('Book')
#models.ForeignKey(AuthorDetail)这里面的外键关联的表,可以不写引号,但是如果不写引号,那么这个外键关联的表必须在写这个外键字段的表上面,一般我们都写上引号,这样就不用考虑哪个表写在上面,哪个表写在下面了
# ad = models.ForeignKey(AuthorDetail, on_delete=models.CASCADE)
# to=可以用写,to_field='id'也可以不用写,自动找到是to=那张表的id字段
#django 1.11 版本的foreignkey 默认有on_delete=models.CASCADE这个属性,所以如果要做这种级联删除模式,可以不用写这个属性,但是django2.0之后,必须要自己手动加上这个属性
# 关于级联(自行丰富)
# models.CASCADE 级联删除 #没办法设置级联更新,要做级联更新,自己通过原生sql去加上,就是修改表
class AuthorDetail(models.Model):
"""作者详细信息表"""
birthday = models.DateField()
telephone = models.CharField(max_length=24)
address = models.CharField(max_length=64)
#通过手机号查找某人 手机号151开头
class Publish(models.Model):
name = models.CharField(max_length=64)
city = models.CharField(max_length=64)
class Book(models.Model):
title = models.CharField(max_length=64)
pub_date = models.DateField()
price = models.DecimalField(max_digits=10,decimal_places=2)
pub = models.ForeignKey('Publish')
# ForeignKey这个类,生成数据库字段的时候,会自动将该属性名称_id作为我们的数据库字段名称
authors = models.ManyToManyField('Author')
#authors在执行数据库同步指令之后,不会生成字段,而是会帮我们生成一个第三张表,这个表就是书籍表和作者表的多对多关系表
# class BookToAuthor(models.Model):
看代码
print(request.POST.dict()) #dict()方法能将QueryDict类型数据转换为普通字典类型数据
#传数据时,可以用**{}打散的方式来传输入,但是如果碰到models中有decimal类型的字段数据,那么update更新时,会对提交的数据进行decimal类型数据转换,# 发现有Decimal数据要存储,会将提交的数据转换为Decimal类型来存储,所有个数据类型强转的过程,导致如果我们直接写**request.POST,会报错,所以引入了request.POST.dict()这个方法,其实如果说不涉及到强制类型转换失败的请求,参数直接写**request.POST就可以
obj_list.update(
**request.POST.dict()
)
#模型类中的pk属性能够自动帮我们找到模型类中的主键字段
创建一对一关系字段时的一些参数
to
设置要关联的表。
to_field
设置要关联的字段。
on_delete
同ForeignKey字段。
创建一对多关系字段时的一些参数
to
设置要关联的表
to_field
设置要关联的表的字段
related_name
反向操作时,使用的字段名,用于代替原反向查询时的'表名_set'。
related_query_name
反向查询操作时,使用的连接前缀,用于替换表名。
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。
创建多对多关系字段时的一些参数
多对多的参数:
to
设置要关联的表
related_name
同ForeignKey字段。
related_query_name
同ForeignKey字段。
through
在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系。
但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过
through来指定第三张表的表名。
through_fields
设置关联的字段。
db_table
默认创建第三张表时,数据库中表的名称。
示例:authors = models.ManyToManyField('Author',db_table='xx')
方式1
手动创建第三张表(没办法是manytomanyfield提供的操作第三张表数据的方法)
# 想操作第三张,就需要自己写sql或者直接对第三张表来添加数据,
# 比如models.Author2Book.objects.create(author_id=1,book_id=1,xx='oo')
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="书名")
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者姓名")
# 自己创建第三张表,分别通过外键关联书和作者
class Author2Book(models.Model):
author = models.ForeignKey(to="Author")
book = models.ForeignKey(to="Book")
xx = models.CharField(max_length=32) #拓展字段
class Meta:
unique_together = ("author", "book")
方式2
# 中介模型,orm提供的有些方法可以用,有些用不了,比如add添加数据的方法
# 手动创建第三张表,并通过ManyToManyField来指定一下这个关系表
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="书名")
# 自己创建第三张表,并通过ManyToManyField指定关联
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者姓名")
books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book"))
# through_fields接受一个2元组('field1','field2'):
# 其中field1是定义ManyToManyField的模型外键的名(author),field2是关联目标模型(book)的外键名。
# 比如author_obj的id为1
author_obj.books.add(1,2)
'''
Author2Book
id author_id book_id xx
1 1 1 怎么添加
2 1 2 怎么添加 add方法搞不定
'''
class Author2Book(models.Model):
author = models.ForeignKey(to="Author")
book = models.ForeignKey(to="Book")
#可以扩展其他的字段了
xx = models.CharField(max_length=32) #拓展字段
class Meta:
unique_together = ("author", "book")
方式3
通过ManyToManyField自动生成第三张表
class Book(models.Model):
title = models.CharField(max_length=32, verbose_name="书名")
# 通过ORM自带的ManyToManyField自动创建第三张表
class Author(models.Model):
name = models.CharField(max_length=32, verbose_name="作者姓名")
books = models.ManyToManyField(to="Book", related_name="authors") #自动生成的第三张表我们是没有办法添加其他字段的
元信息
ORM对应的类里面包含另一个Meta类,而Meta类封装了一些数据库的信息。主要字段如下:
class Author2Book(models.Model):
author = models.ForeignKey(to="Author")
book = models.ForeignKey(to="Book")
class Meta:
unique_together = ("author", "book")
db_table
ORM在数据库中的表名默认是 app_类名,可以通过db_table可以重写表名。db_table = 'book_model'
index_together
联合索引。
unique_together
联合唯一索引。
ordering
指定默认按什么字段排序。
ordering = ['pub_date',]
只有设置了该属性,我们查询到的结果才可以被reverse(),否则是能对排序了的结果进行反转(order_by()方法排序过的数据)
示例:
title = models.CharField(max_length=64,db_column='xx')
author = models.ForeignKey(to="Author",db_column='ss')
on_delete
当删除关联表中的数据时,当前表与其关联的行的行为。
models.CASCADE
删除关联数据,与之关联也删除
models.DO_NOTHING
删除关联数据,引发错误IntegrityError
models.PROTECT
删除关联数据,引发错误ProtectedError
models.SET_NULL
删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
#pub = models.ForeignKey('Publish',on_delete=models.SET_NULL, null=True)
models.SET_DEFAULT
删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
models.SET
删除关联数据,
a. 与之关联的值设置为指定值,设置:models.SET(值)
b. 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)
关系和约束大家要搞清楚,我不加外键能不能表示两个表之间的关系啊,当然可以
但是我们就不能使用ORM外键相关的方法了,所以我们单纯的将外键换成一个其他字段类型,只是单纯的存着另外一个关联表的主键值是不能使用ORM外键方法的。
#db_constraint=False只加两者的关系,没有强制约束的效果,并且ORM外键相关的接口(方法)还能使用,所以如果将来公司让你建立外键,并且不能有强制的约束关系,那么就可以将这个参数改为False
customer = models.ForeignKey(verbose_name='关联客户', to='Customer',db_constraint=False)
看示例
# 一对一
# 如果使用的是模型类的关系属性名称来添加数据,那么属性名称对应的值必须是关联表中的某条记录的模型类对象
# au_obj = models.AuthorDetail.objects.get(id=1)
# models.Author.objects.create(
# name='刘伟',
# age=26,
# ad=au_obj #其实存在数据库中的还是au_obj的id值
# )
# 如果使用数据库表字段的形式来创建关系记录数据,那么需要使用数据库中表字段名称来指定数据(用的居多)
# au_obj = models.AuthorDetail.objects.get(id=1)
# models.Author.objects.create(
# name='王振',
# age=16,
# ad_id=2 # 直接使用关联表中的某条记录的id值的方式
# )
############一对多##############
# book表和publish表是多对一的关系
# 添加记录和上面的一对一一样
# 写法1
# publish_obj = models.Publish.objects.get(id=1)
# models.Book.objects.create(
# title='娇艳人生',
# pub_date='2008-09-09',
# price=88.88,
# pub=publish_obj #某个出版社的模型类对象
# )
# 写法2
# models.Book.objects.create(
# title='金鳞',
# pub_date='2008-09-09',
# price=82.82,
# pub_id=1 #某个出版社记录的id值
# )
######### 多对多 #############
# 作者表和书籍表是多对多关系
# 少年阿宾 -- 谢思敏和谢晨
# book_obj = models.Book.objects.create(
# title='少年阿宾',
# pub_date='2020-07-10',
# price=2,
# pub_id=1
# )
# author_obj1 = models.Author.objects.create(
# name='谢思敏',
# age=16,
# ad_id=3 # 直接使用关联表中的某条记录的id值的方式
# )
# author_obj2 = models.Author.objects.create(
# name='谢晨',
# age=16,
# ad_id=4 # 直接使用关联表中的某条记录的id值的方式
# )
# 方式1
# book_obj.authors.add(author_obj1,author_obj2) #写对应作者的模型类对象
# 方式2
# book_obj.authors.add(1, 4) #直接写作者记录的id值
# 方式3
book_obj = models.Book.objects.get(id=1)
book_obj.authors.add(*[2, 3]) #直接写作者记录的id值
'''
app01_book_authors
id book_id author_id
1 3 3
2 3 4
'''
看示例
# 修改
# 在一对一和一对多关系时,和单表操作是一样的
# 一对一
# models.Author.objects.filter(id=1).update(name='xx',ad=模型类对象)
# models.Author.objects.filter(id=1).update(name='xx',ad_id=2)
# pub_obj = models.Publish.objects.get(id=2)
# 一对多
# models.Book.objects.filter(id=1).update(pub=pub_obj)
# models.Book.objects.filter(id=2).update(title='金鳞第二部',pub_id=2)
#删除
# delete方法
# models.Author.objects.filter(id=1).delete()
# models.AuthorDetail.objects.filter(id=1).delete()
# 一对一和一对多删除一样
# 多对多更新和删除
# 针对关系记录的操作
# book_obj = models.Book.objects.get(id=1)
# author_obj = models.Author.objects.get(id=2)
# 删除remove 在多对多关系表中删除了书籍id为1的,作者id为2的记录删除
# book_obj.authors.remove(2) # 删除单条
# book_obj.authors.remove(2, 3) # 删除多条
# book_obj.authors.remove(author_obj) # 按照模型类对象为参数进行删除
# 清空clear
# book_obj.authors.clear() #将当前书籍对应的所有作者在多对多关系表中的关系记录,全部删除
# 更新(修改)
# book_obj = models.Book.objects.get(id=4)
# book_obj.authors.set('3') #参数是可迭代类型数据
# book_obj.authors.set([3, ]) #更新多个记录
# book_obj = models.Book.objects.get(id=1)
# book_obj.authors.set([4, ])
# set动作有两步:1 先执行clear 2 再执行add添加
子查询和连表查询
# 查询
# 一对一查询
# 正向查询
# 关系属性写在哪个表里面,那么通过这个表的数据,去查询关联的另外一张表的数据,就叫做正向查询,反之就是反向查询
# 正向查询使用关联属性名称
# 查询一下王振这个作者的手机号
# author_obj = models.Author.objects.get(name='王振')
# # author_obj.ad #找到了author_obj关联的作者详细信息表里面对应记录
# print(author_obj.ad.telephone) # 120
# 反向查询
# 反向查询用关联它的模型类的名称小写
# 查询一下地址在上海的那个作者是谁
# author_detail_obj = models.AuthorDetail.objects.filter(address='上海').first()
# print(author_detail_obj.author.name)
##########一对多#########
# 正向查询
# 使用关联属性查询
# 查询一下少年阿宾是哪个出版社出版的
# book_obj = models.Book.objects.get(title='少年阿宾')
# print(book_obj.pub.name)
# 反向查询
# 模型类名小写_set
# 查询一下伟哥出版社出版了哪些书
# pub_obj = models.Publish.objects.get(name='伟哥出版社')
# pub_obj.book_set # 可能为多条记录,所以模型类名小写_set
# 类似于objects控制器
# print(pub_obj.book_set.all().values('title'))
# 查询结果不会自动去重
######多对多######
# 正向查询
# 使用属性来查
# 查询一下金鳞第二部这本书是谁写的
# book_obj = models.Book.objects.get(title='金鳞第二部')
# print(book_obj.authors.all().values('name'))
# 反向查询
# 使用模型类名小写_set
# 查询一下谢晨写了哪些书
# author_obj = models.Author.objects.get(name='谢晨')
# print(author_obj.book_set.all().values('title'))
1 query
2 在settings配置文件中写上如下内容,就能够自动打印出我们执行orm语句对应的sql语句
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'console':{
'level':'DEBUG',
'class':'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'propagate': True,
'level':'DEBUG',
},
}
}
3 通过django配置的连接mysql的管道来查看(pymysql)
from app01 import models
def add_book(request):
'''
添加表记录
:param request: http请求信息
:return:
'''
book_obj = models.Book(title='python',price=123,pub_date='2012-12-12',publish='人民出版社')
book_obj.save()
from django.db import connection #通过这种方式也能查看执行的sql语句
print(connection.queries)
return HttpResponse('ok')
看示例
###############基于双下划线的跨表查询##################
# 连表操作
# select * from t2 inner join t1 on t1.t2_id=t2.id
# select * from t1 inner join t2 on t1.t2_id=t2.id
# 一对一
# 查询一下谢思敏这个作者的家庭住址
# 正向操作,使用属性
# ret = models.Author.objects.filter(name='谢思敏').values('ad__address')
#
# 方向操作,表名小写
# ret = models.AuthorDetail.objects.filter(author__name='谢思敏').values('address')
#
# 一对多
# 查询一下少年阿宾是哪个出版社出版的
# 正向操作 使用关联属性
# ret = models.Book.objects.filter(title='少年阿宾').values('pub__name')
#
# 反向操作
# ret = models.Publish.objects.filter(book__title='少年阿宾').values('name')
#
# 多对多
# 查询一下金鳞第二部这本书是谁写的
# 正向操作 使用关联属性
# ret = models.Book.objects.filter(title='金鳞第二部').values('authors__name')
#
# ret = models.Author.objects.filter(book__title='金鳞第二部').values('name')
#
# print(ret)
##########聚合查询aggregate###########
# 统计一下所有书籍的平均价格 max min avg count sum
# ret = models.Book.objects.aggregate(Avg('price'))
# print(ret) # 结果是普通字典类型:{'price__avg': 43.925}
# ret = models.Book.objects.all().aggregate(a=Avg('price'), b=Max('price'))
# print(ret) # {'price__avg': 43.925, 'price__max': Decimal('88.88')} -- {'a': 43.925, 'b': Decimal('88.88')}
# aggregate方法可以看为是orm语句的结束语句,结果为普通字典类型,不能在继续调用queryset或者模型类对象提供的方法了
Group by
###########分组查询###########
# 查询一下每个出版社出版书的平均价格
# ret = models.Book.objects.values('pub_id').annotate(a=Avg('price')) #只能获取到values指定的字段和统计结果数据
# ret = models.Book.objects.values('pub_id','id').annotate(a=Avg('price')) # 多条件分组pub_id和id值相同的算为1组
# ret = models.Book.objects.values('pub__name').annotate(a=Avg('price')) # 以出版社名称分组
# print(ret)
# ret = models.Publish.objects.annotate(a=Avg('book__price')) #返回结果是Publish的模型类对象,这个模型类对象里面包含Publish的所有属性数据,还有annotate的统计结果数据
ret = models.Publish.objects.annotate(a=Avg('book__price')).values('name', 'a')
# print(ret)
# 原生sql,伪代码
# select publish.name,Avg(book.price) from publish inner join book on publish.id=book.pub_id group by publish.id;
# select avg(price) from book group by pub_id;
主要是针对本表的多个字段进行比较时使用
看示例:
########F查询#########
from django.db.models import F
# 查询一下点赞数大于评论数的书籍
# models.Book.objects.filter(dianzan__gt=comment)
# obj_list = models.Book.objects.all().values()
# a = []
# for i in obj_list:
# if i.dianzan > i.comment:
# a.append(i)
# print(a)
# F查询可以用来做本表不同字段之间的一个比较
# ret = models.Book.objects.filter(dianzan__gt=F('comment'))
# print(ret)
# F可以用来对本表数据进行一些统一操作(四则运算都支持)
# 将所有的书籍上调10块钱
models.Book.objects.all().update(price=F('price')+10)
# models.Book.objects.all().update(price=100)
可以进行多条件查询,查询关系可以是 或与非
看示例:
from django.db.models import Q
#查询书名中包含少年两个字的并且评论数大于20的书籍
# ret = models.Book.objects.filter(title__contains='少年',comment__gt=20) #filter中逗号分隔的条件,默认是and的关系
# ]>
# 想进行或的关系查询需要借助到我们Q查询Q
# 查询书名中包含少年两个字的或者评论数大于20的书籍Q
# ret = models.Book.objects.filter(Q(title__contains='少年') | Q(comment__gt=20))
# 查询书名中包含少年两个字的或者评论数大于20的,并且点赞数大于等于80的
# ret = models.Book.objects.filter(Q(title__contains='少年') | Q(comment__gt=20), dianzan__gte=80)
# 注意,如果结合逗号来进行and的关系查询,那么必须将没有Q包裹的查询条件放在Q包裹的查询条件后面
#下面这种方式也可以,Q查询可以多层嵌套使用
# ret = models.Book.objects.filter(Q(Q(title__contains='少年') | Q(comment__gt=20)) & ~Q(dianzan__gte=80)) #~ 条件取反 ,&--and关系 ,|--or关系
# print(ret)
#1 查询每个作者的姓名以及出版的书的最高价格
#2 查询作者id大于2作者的姓名以及出版的书的最高价格
#3 查询作者id大于2或者作者年龄大于等于20岁的女作者的姓名以及出版的书的最高价格
#4 查询每个作者出版的书的最高价格 的平均值
#5 每个作者出版的所有书的最高价格以及最高价格的那本书的名称.
方式1:raw
ret = models.Book.objects.raw('select * from app01_book;')
#raw只能操作前面表的数据,比如Book
print(ret)
for i in ret:
print(i,type(i))
方式2
from django.db import connection
cursor = connection.cursor()
cursor.execute('select * from app01_book;')
print(cursor.fetchall())
看示例
xx.py文件,随便建一个文件,单独运行这个文件时,想获取到django的内容环境内容,需要下面的写法
import os
if __name__ == '__main__':
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_orm2.settings")
import django
django.setup()
from app01 import models
print(models.Book.objects.all())
加锁写法,必须用在事务里面
models.Book.objects.select_for_update().all()
'''
select * from app01_book for update;
'''
事务写法:
方式1
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'mxshop',
'HOST': '127.0.0.1',
'PORT': '3306',
'USER': 'root',
'PASSWORD': '123',
"ATOMIC_REQUESTS": True, #全局开启事务,绑定的是http请求响应整个过程
},
}
方式2 给视图函数直接加装饰器,表示整个视图逻辑中的sql都捆绑为了一个事务操作
from django.db import transaction
# sql一旦出错,会自动回滚
@transaction.atomic
def viewfunc(request):
sid = transaction.savepoint() #创建保存点
# This code executes inside a transaction.
do_stuff() #事务和变量等处理逻辑是没关系的,d['xx'] = 'oo'
....
do_other_stuff()
transaction.savepoint_rollback(sid) #回滚保存点
#transaction.savepoint_commit(sid) #提交保存点
方式3 给逻辑加视图
from django.db import transaction
def viewfunc(request):
# This code executes in autocommit mode (Django's default).
do_stuff()
with transaction.atomic(): # 加事务
# This code executes inside a transaction.
do_more_stuff()
#models.Book.objects.select_for_update().all()
do_other_stuff()
set sql_mode='STRICT_TRANS_TABLES'; #先将当前会话的sql_mode的only_full_group_by模式去掉
select * from (SELECT app01_book.xx,app01_book.price,app01_author.id from app01_author
INNER join app01_book_authors on app01_author.id = app01_book_authors.author_id
INNER JOIN app01_book on app01_book_authors.book_id = app01_book.id ORDER BY app01_book.price desc LIMIT 100)
as t GROUP BY t.id;
select * from (SELECT app01_book.xx,app01_book.price,app01_author.id from app01_author
INNER join app01_book_authors on app01_author.id = app01_book_authors.author_id
INNER JOIN app01_book on app01_book_authors.book_id = app01_book.id HAVING 1=1 ORDER BY app01_book.price desc)
as t GROUP BY t.id;
5.6的版本使用下面的答案
select * from (SELECT app01_book.xx,app01_book.price,app01_author.id from app01_author
INNER join app01_book_authors on app01_author.id = app01_book_authors.author_id
INNER JOIN app01_book on app01_book_authors.book_id = app01_book.id ORDER BY app01_book.price desc)
as t GROUP BY t.id;
他是js的功能,特点:异步请求,局部刷新
简单请求示例
基于jquery的ajax请求
异步请求,不会刷新页面,页面上用户之前输入的数据都不会丢失。
下面是ajax请求
通过浏览器控制台分析ajax请求的方式
Title
下面是ajax请求
$.ajax({ //
url:'/login_ajax/', //写路径时,如果后台使用的是django框架,那么url路径的后面的斜杠要加上,如果想不加上斜杠,那么需要在django的settings配置文件中加上 APPEND_SLASH = False,并且后台的urls.py文件中的路径要和ajax请求路径对应好,该有斜杠写斜杠,不需要斜杠的,去掉斜杠
type:'post',
...
}),
$.ajax({ //
url:'{% url "login_ajax" %}',
type:'post',
...
}),
但是,要注意一点,当这段js代码是写到某个js文件中,然后hmtl文件通过script标签的src属性来引入时,你的{% url "login_ajax" %}语法就不能被模板渲染了,也就是失效了
示例
list.html文件内容如下
$('#btn').click(function () {
$.ajax({
url:'/data/',
type:'get',
success:function (res) {
console.log(res,typeof res);
//{"name": "xx", "hobby": ["女", "gay"]} string
var res_dict = JSON.parse(res);
console.log(res_dict,typeof res_dict);
{#var hobby = res['hobby'];#}
{#var hobby = res_dict['hobby']; //["女", "gay"]#}
var hobby = res_dict.hobby; //["女", "gay"]
console.log(hobby); // undefined
views.py文件内容如下
import json
def data(request):
data_list = {'name': 'xx', 'hobby': ['女', 'gay']}
data_list_str = json.dumps(data_list, ensure_ascii=False)
# 直接使用HttpResponse回复字典类型数据,那么会将字典里面元素的键都拼接成一个字符串来响应,所以不是我们想要的结果,所以我们先将字典类型数据,转换成json字符串,在通过HttpResponse来进行响应
# return HttpResponse(data_list) #namehobby
return HttpResponse(data_list_str) #namehobby
Urls.py内容如下
url(r'^data/', views.data),
示例
views.py
import json
def data(request):
data_list = {'name': 'xx', 'hobby': ['女', 'gay']}
# 第一种方式,直接通过HttpResponse回复字典数据,那么ajax接受到数据之后,需要自行反序列化
# data_list_str = json.dumps(data_list, ensure_ascii=False)
# 直接使用HttpResponse回复字典类型数据,那么会将字典里面元素的键都拼接成一个字符串来响应,所以不是我们想要的结果,所以我们先将字典类型数据,转换成json字符串,在通过HttpResponse来进行响应
# return HttpResponse(data_list) #namehobby
# return HttpResponse(data_list_str) #namehobby
#第二种方式:通过HttpResponse回复字典数据,回复之前,加上一个响应头键值对,如下,那么ajax收到这个响应数据的时候,会查看这个响应头,发现content-type这个响应头的值为application/json,那么会自动对响应数据进行反序列化,不需要我们自己手动反序列化了
#手动加上ret['content-type'] = 'application/json'响应头
# ret = HttpResponse(data_list_str)
# ret['content-type'] = 'application/json'
# return ret
#第三种:
#JsonResponse:1 序列化数据 2 加上['content-type'] = 'application/json'这个响应头键值对
return JsonResponse(data_list)
注意:
hobby_list = ['女', 'gay']
#当使用JsonResponse回复非字典类型数据时,需要将safe参数的值改为False
return JsonResponse(hobby_list, safe=False)
请求消息格式和请求方法没有关系
和请求头键值对中的这一组键值对有关系
Content-Type: application/x-www-form-urlencoded; //浏览器发送数据ajax或者form表单,默认的格式都是它
它表示请求携带的数据格式,application/x-www-form-urlencoded对应的数据格式是:a=1&b=2
cookie解析
http协议是无状态的,无连接的。
导致每次客户端访问服务端需要登录成功之后才能访问的页面,都需要用户才重新登 录一遍,用户体验极差。
客户端想了个办法,cookie,小甜点。
获取cookie
request.COOKIES.get('is_login')
设置cookie
ret = redirect('/home/') #302 /home/
# ret = HttpResponse('ok')
# ret = render(request,'home.html')
# 设置cookie
ret.set_cookie('is_login', True)
ret.set_cookie('name', 'root') #设置同名的cookie,就是修改cookie
return ret
设置签名cookie
ret.set_signed_cookie('name', 'root',salt='sksk')
获取签名cookie
request.get_signed_cookie('name',salt='sksk') #以防cookie在传输过程中被人修改了
删除cookie
def logout(request):
ret = redirect('login')
ret.delete_cookie('is_login')
return ret
参数:
key, 键
value='', 值
max_age=None, 超时时间 值一个数字,单位是秒
expires=None, 超时时间(IE requires expires, so set it if hasn't been already.) 值是日期时间类型数据
path='/', Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
当:path='/index',
127.0.0.1:8001/home cookie 不生效,获取不到
127.0.0.1:8001/index/xxx/xx/ggg 生效,凡是访问/index路径下面的子路路径,都能获取到这个cookie
domain=None, Cookie生效的域名
secure=False, https传输
httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)
# 方式1
def login(request):
ret = HttpResponse('ok')
ret.set_cookie('k1','你好'.encode('utf-8').decode('iso-8859-1'))
#取值:request.COOKIES['k1'].encode('utf-8').decode('iso-8859-1').encode('iso-8859-1').decode('utf-8')
return ret
方式2 json
def login(request):
ret = HttpResponse('ok')
import json
ret.set_cookie('k1',json.dumps('你好'))
#取值 json.loads(request.COOKIES['k1'])
return ret
- Cookie大小上限为4KB;
- 一个服务器最多在客户端浏览器上保存20个Cookie;
- 一个浏览器最多保存300个Cookie,因为一个浏览器可以访问多个服务器。
- cookie是明文存储在客户端的,不安全,所以一些敏感数据是不应该放到cookie里面的
上面的数据只是HTTP的Cookie规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等!但也不会出现把你硬盘占满的可能!
注意,不同浏览器之间是不共享Cookie的。也就是说在你使用IE访问服务器时,服务器会把Cookie发给IE,然后由IE保存起来,当你在使用FireFox访问服务器时,不可能把IE保存的Cookie发送给服务器。
针对以上特点cookie有数据大小限制,还明文存储在客户端,出现了session技术
session技术就是为了提升上面两个特点。
基于cookie技术(目前先这样里面,其实不通过cookie也能完成session技术)
session特点:
1.基于cookie时,也是以键值对的形式来存的数据,但是数据是密文的,只是一把钥匙(随机字符串)
2.真正的数据是保存在服务端的,将来通过cookie带过来的这个随机字符串,来找到服务端保存的对应用户的数据,数据既然是保存在服务端的,那么数据大小没有限制
设置值
request.session['is_login'] = True #等于别的值,就是修改动作
request.session['username'] = 'root'
'''
request.session['is_login'] = True
做了三步事情
1. 生成一个随机字符串,并将这个字符串添加到了cookie里面,键值对名称这样的: sessionid:ljlijsoidjoasdiog
2. 将is_login和username这两个键值对数据{'is_login':True,'username':'root'}首先进行了json序列化,然后进行了加密,然后将加密后的数据和sessionid对应的随机值保存到了django-sesison表中。
3. 给session保存的数据,加了有效期,默认是两周
'''
取值
request.session['k1']
is_login = request.session.get('is_login')
'''
request.session['is_login']
1.去cookie里面取出sessionid这个键对应的值(随机字符串)
2.通过这个随机字符串去django-session表里面获取对应的数据,session_data那一列的数据
3.对数据进行解密和反序列化,得到{'is_login':True,'username':'root'},然后通过键取出对应的值
'''
删除值
request.session.flush()
#del request.session['k1']
'''
1 删除cookie中的sessionid值
2 删除django-sesion表中保存的数据记录
'''
# 所有 键、值、键值对
request.session.keys()
request.session.values()
request.session.items()
#获取sessionid的值
session_key = request.session.session_key
# 设置会话Session和Cookie的超时时间
request.session.set_expiry(value)
* 如果value是个整数,session会在些秒数后失效。
* 如果value是个datatime或timedelta,session就会在这个时间后失效。
* 如果value是0,用户关闭浏览器session就会失效。
* 如果value是None,session会依赖全局session失效策略。
1. 数据库Session
SESSION_ENGINE = 'django.contrib.sessions.backends.db' # 引擎(默认)
2. 缓存Session
SESSION_ENGINE = 'django.contrib.sessions.backends.cache' # 引擎
SESSION_CACHE_ALIAS = 'default' # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
3. 文件Session
SESSION_ENGINE = 'django.contrib.sessions.backends.file' # 引擎
SESSION_FILE_PATH = None # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()
4. 缓存+数据库
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db' # 引擎
其他公用设置项:
SESSION_COOKIE_NAME = "sessionid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径(默认)
SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名(默认)
SESSION_COOKIE_SECURE = False # 是否Https传输cookie(默认)
SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输(默认)
SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周)(默认)
SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期(默认)
一个浏览器对应一个服务端,只有一个session。
写一个session的登录认证装饰器
通过sweetalert插件配合ajax,完成图书管理系统的删除动作,并且不允许刷新页面
看示例
def check_login(f):
def inner(request, *args, **kwargs):
is_login = request.COOKIES.get('is_login')
if is_login == 'True':
ret = f(request)
return ret
else:
return redirect('login')
return inner
@check_login
def cart(request):
# is_login = request.COOKIES.get('is_login')
# if is_login == 'True':
return render(request, 'cart.html')
看示例
def check_login(f):
def inner(request, *args, **kwargs):
is_login = request.session.get('is_login')
if is_login:
ret = f(request, *args, **kwargs)
return ret
else:
return redirect('login')
return inner
#使用
@check_login
def cart(request):
return render(request, 'cart.html')
普通函数:var k = function(参数){return 'kkkk'};
箭头函数:var k = (参数) =>{return 'kkkk'};
箭头函数简写: var k = (参数)=>'kkkk'
var a = 'xx';
var d1 = {
a:'ss',
f: () => {
//this指向函数调用者
// console.log(this);
console.log(this.a);
}
}
d1.f() #调用者是d1对象,所以打印的内容为ss
# 函数的单体模式
var a = 'xx';
var d1 = {
a:'ss',
f(){
console.log(this.a); //结果为:ss
},
}
d1.f()
# 箭头函数,改变this的指向,将this指向上下文的this
var a = 'xx';
var d1 = {
a:'ss',
f:()=>{
console.log(this); // 指向了Window对象
console.log(this.a); //结果为xx
f1()
},
}
d1.f()
//
var a = 'xx';
var d1 = {
a:'ss',
f:function(){
console.log(this.a); // ss
function f1(){
console.log(this); // 指向了Window对象
console.log(this.a); //结果为xx
}
f1()
},
}
d1.f()
//箭头函数改变this指向
var a = 'xx';
var d1 = {
a:'ss',
f:function(){
console.log(this.a); // ss
var f1 = ()=>{
console.log(this); // 指向了d1对象
console.log(this.a); //结果为ss
}
f1()
},
}
d1.f()
//箭头函数改变this指向
var a = 'xx';
var d1 = {
a:'ss',
f:()=>{
console.log(this.a); // xx
var f1 = ()=>{
console.log(this); // 指向了window对象
console.log(this.a); //结果为xx
}
f1()
},
}
d1.f()
$(".del").on("click", function () {
var delete_id = $(this).prev().text();
var ths = $(this); //因为下面的函数中this指向的不是我们的标签按钮,所以通过一个变量来保存一下这个$(this),方便后面使用
swal({
title: "are you sure?",
text: "要谨慎!",
type: "warning",
showCancelButton: true,
confirmButtonClass: "btn-danger",
confirmButtonText: "确定删除",
cancelButtonText: "容我三思",
closeOnConfirm: false
},
//点击确认删除时触发的函数
function () {
$.ajax({
// 路径拼接
url:'/ajax_delete_book/'+ delete_id +'/', //'/ajax_delete_book/1/'
type:'get',
success:function (res) {
console.log(res);
if (res === 'ok'){
//关闭弹框
swal("删除成功!", "你可以准备跑路了!", "success");
//局部刷新,删除对应记录
ths.parent().parent().remove();
{#console.log(ths);#}
}
}
})
});
})
$.ajax({
url:'/ajax_delete_book/'+ '100' +'/', //'/ajax_delete_book/1/'
type:'get',
success:function (res) { //当状态码为2xx或者3xx时才认为是请求正常成功了,会触发success对应的函数。但是当状态为4xx、5xx的时候,ajax认为此次请求是失败的会触发error属性对应的函数
{#console.log(res);#}
if (res === 'ok'){
console.log('okokokok');
//关闭弹框
swal("删除成功!", "你可以准备跑路了!", "success");
//局部刷新,删除对应记录
ths.parent().parent().remove();
{#console.log(ths);#}
}else {
console.log('出错啦!')
}
},
error:function (res) {
{#console.log(res);#}
{#console.log(res.status);#}
if (res.status === 404){
swal("删除失败!", res.responseText, "error");
//res.responseText是后台响应回来的错误信息
}
}
})
示例:
def ajax_delete_book(request, pk):
try:
models.Book.objects.get(pk=pk).delete()
except:
# return HttpResponse('error') #默认响应的状态码都是200
return HttpResponse('找不到对应的资源!!!!!', status=404) # 将默认响应状态码改成404
return HttpResponse('ok')
跨站请求伪造,这一种攻击手段
login.html文件
{% csrf_token %} 当我们使用form表单标签来发送请求时,如果需要通过csrftoken认证,那么必须将它写到我们的form表单标签里面,里面的任意位置
html页面中的{% csrf_token %}
cookie中的
csrftoken:CeFG6SA8Y8hcHAX5R93sxrS37v3iFFlcvX8xjfaRHjLlgFaKRbzXVnSJbGwHHqO9
django会取出提交数据部分的token值和cookie中的token值进行如下比较:
WVHKQeAuMS4RGqyLybryIBAfacDa1Dp7PEaB3Badv3y0fvLqydX36xAVen6z3oS4 -- 前32位可以对后面的几位进行解密,解密出以secret_key1 一个字符串
CeFG6SA8Y8hcHAX5R93sxrS37v3iFFlcvX8xjfaRHjLlgFaKRbzXVnSJbGwHHqO9 -- 前32位可以对后面的几位进行解密,解密出以secret_key2 一个字符串
secret_key1 = secret_key2
说明:
token字符串的前32位是salt, 后面是加密后的token, 通过salt能解密出唯一的secret。
django会验证表单中的token和cookie中token是否能解出同样的secret,secret一样则本次请求合法。
MIDDLEWARE = [
...
'django.middleware.csrf.CsrfViewMiddleware',
...
]
源码
def _compare_salted_tokens(request_csrf_token, csrf_token):
# Assume both arguments are sanitized -- that is, strings of
# length CSRF_TOKEN_LENGTH, all CSRF_ALLOWED_CHARS.
return constant_time_compare(
_unsalt_cipher_token(request_csrf_token),
_unsalt_cipher_token(csrf_token),
)
def _unsalt_cipher_token(token):
"""
Given a token (assumed to be a string of CSRF_ALLOWED_CHARS, of length
CSRF_TOKEN_LENGTH, and that its first half is a salt), use it to decrypt
the second half to produce the original secret.
"""
salt = token[:CSRF_SECRET_LENGTH]
token = token[CSRF_SECRET_LENGTH:]
chars = CSRF_ALLOWED_CHARS
pairs = zip((chars.index(x) for x in token), (chars.index(x) for x in salt))
secret = ''.join(chars[x - y] for x, y in pairs) # Note negative values are ok
return secret
方式1
首先在html文件中间,写上我们的{% csrf_token %}
$('#btn').click(function () {
var uname = $("#uname").val();
// 获取csrfmiddlewaretoken的input标签value属性对应的值
var token = $('[name="csrfmiddlewaretoken"]').val();
$.ajax({
url:'/login/',
type:'post',
//将token值放到请求数据部分
data:{uname:uname,csrfmiddlewaretoken:token},
success:function (res) {
console.log(res);
}
})
})
方式2:
使用'{
{ csrf_token }}' 这个模板渲染标签,这样就不要在我们html页面中写{% csrf_token %}了。
$('#btn').click(function () {
//方式1
var uname = $("#uname").val();
// 直接就能得到csrfmiddlewaretoken的input标签value属性对应的值
var token = '{
{ csrf_token }}';
$.ajax({
url:'/login/',
type:'post',
data:{uname:uname,csrfmiddlewaretoken:token},
success:function (res) {
console.log(res);
}
})
})
方式3
借助js操作cookie的方法来获取到cookie中的csrftoken那个键对应的值
然后将这个值组成一个请求头,放到此次请求中
headers:{"X-CSRFToken":这个值}
这个值,通过js能够获取,通过jquery也能获取,我们看一下jquery如何操作cookie
jquery操作cookie参考:https://www.cnblogs.com/clschao/articles/10480029.html
下载网址:http://plugins.jquery.com/cookie/
$('#btn').click(function () {
//方式3
// 前提:需要在html页面中写上{% csrf_token %}或者{
{ csrf_token }},不然此次获取这个html页面的时候,响应中不会有这个csrftoken的cookie值
var uname = $("#uname").val();
// 通过js或者jquery来获取cookie中的csrftoken这个键对应的token值
var token = $.cookie('csrftoken');
$.ajax({
url:'/login/',
type:'post',
// 将获取到的token值放到请求头中,这个请求头键值对的的键必须是"X-CSRFToken"
headers:{
"X-CSRFToken":token,
},
// django先去获取请求数据部分的token值,获取不到,就去找一个叫做X-CSRFToken请求头键值对,他的值和cookie中的csrftoken的值要相等。
data:{uname:uname,},
success:function (res) {
console.log(res);
}
})
})
代码示例
html部分
用户名:
密码:
头像:
js部分
$('#sub').click(function () {
//ajax上传文件必须依赖于FormData对象
var formdata = new FormData();
var uname = $('[name="username"]').val();
var pwd = $('[name="password"]').val();
// 获取浏览器上的文件数据方法:$('[name="touxiang"]')[0].files[0]
var file_obj = $('[name="touxiang"]')[0].files[0];
{#formdata.append('xx','oo') //django 获取数据:post request.POST.get('xx') -- oo#}
//request.POST
formdata.append('uname',uname)
formdata.append('pwd',pwd)
{# {% csrf_token %}#}
formdata.append('csrfmiddlewaretoken','{
{ csrf_token }}')
//request.FILES
formdata.append('touxiang',file_obj)
$.ajax({
{#url:'/upload/',#}
url:'', //如果路径为空,那么使用当前页面的路径,也就是往当前页面的路径下进行提交
type:'post',
data:formdata,
// 下面的两个参数的意思是,不要对数据进行任何的预处理和加工
// 固定写法
processData:false,
contentType:false,
success:function (res) {
console.log(res);
}
})
})
标签写法:
下载:http://plugins.jquery.com/cookie/
引入
cookie操作
设置值:
$.cookie('the_cookie', 'the_value');
# 设置7天有效期
$.cookie('the_cookie', 'the_value', { expires: 7 });
读取cookie
$.cookie('the_cookie'); #通过键来取值
删除cookie
$.cookie('the_cookie', null); //通过传递null作为cookie的值即可
项目主目录中的配置文件是django留给开发者使用的用户级别配置文件
实际上django有一个自己的默认全局配置文件。
那么他们的关系如下
# django默认配置
from django.conf import global_settings
# 用户级别配置
from django_middleware import settings
from django.conf import settings # 中间人
# 比如需要引入配置中的某个配置项settings.APPEND_SLASH,那么这样的引入的查找顺序是这样,先去用户级别settings.py文件中去找这个配合,如果没有找到,那么会去global_settings中去找默认配置
登录认证、权限认证、频率访问限制,请求数据统计等等。。。
自定义中间件的步骤
1 在应用文件夹下面创建一个文件夹,名字随便,比如叫做mymiddleware
2 在mymiddleware文件夹下面创建一个py文件,名称随意,比如叫做middlewares.py
3 在middlewares.py文件中写上如下内容
from django.utils.deprecation import MiddlewareMixin
class LoginAuth(MiddlewareMixin):
def process_request(self,request):
print('xxxxxx')
4 settings.py文件中配置如下内容
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',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# 配置中间件类,告诉django,我写的这个自定义中间件,你帮我使用上,一般都放到最后,不然上面几个中间件的相关功能就没办法使用上了
'app01.mymiddleware.middlewares.LoginAuth',
# 如果中间件功能不想用了,就注释它就行了
# 'app01.mymiddleware.middlewares.LoginAuth'
]
这样几部搞定之后,所有的请求都会触发我们的中间件类中的process_request方法。
中间件代码如下
# 登录认证中间件
class LoginAuth(MiddlewareMixin):
# /login/登录请求,应该正常放行,让他能获得login.html页面
# 白名单、黑名单
white_list = ['/login/', ]
def process_request(self,request):
print('请求来啦!!!')
# 获取当前请求路径:request.path /home/
# 如果当前请求的路径在白名单里面,我们不进行登录认证
if not request.path in self.white_list:
is_login = request.session.get('is_login') #True
if not is_login:
return redirect('/login/')
视图代码如下
def login(request):
if request.method == 'GET':
return render(request, 'login.html')
else:
print('xxxxx')
uname = request.POST.get('username')
if uname == 'root':
request.session['is_login'] = True
return redirect('/home/') #告诉浏览器向/home/发送get请求获取页面
else:
return redirect('/login/')
def home(request):
print('home')
return render(request, 'home.html')
def index(request):
print('index')
return render(request, 'index.html')
方法如下:process_request和process_response这两个是重点
- process_request(self,request)
- process_view(self, request, view_func, view_args, view_kwargs)
- process_template_response(self,request,response)
- process_exception(self, request, exception)
- process_response(self, request, response)
针对这个五个方法,我们来看看这5个方法的执行流程。
1 process_request
class Md1(MiddlewareMixin):
def process_request(self,request):
print('MD1--process_request')
# 当process_request返回的是None,那么才会继续执行后面的中间件的process_request,如果你是最后一个中间件,并且你返回的是None,那么逻辑会继续执行到我们的url路由控制器
# 但是当process_request里面return的是一个HttpResponse对象,那么后面的中间件的process_request将不再执行,也不会走到url路由控制器
return HttpResponse('中间件md1的逻辑,没有通过!!!!')
class Md2(MiddlewareMixin):
def process_request(self, request):
print('MD2--process_request')
----------------------------------------------------------------
2 process_response
class Md1(MiddlewareMixin):
def process_request(self,request):
print('MD1--process_request')
# 当process_request返回的是None,那么才会继续执行后面的中间件的process_request,如果你是最后一个中间件,并且你返回的是None,那么逻辑会继续执行到我们的url路由控制器
# 但是当process_request里面return的是一个HttpResponse对象,那么后面的中间件的process_request将不再执行,也不会走到url路由控制器
return HttpResponse('中间件md1的逻辑,没有通过!!!!')
def process_response(self, request, response):
'''
:param request: 当前请求对象
:param response: 视图函数的响应对象
:return:
'''
print('Md1--响应')
# print(response) #
# 对响应内容做统一处理
# response['xx'] = 'oo' #统一加响应头
# print(response.content) #b'ok'
# 注意,如果你写了process_response方法,那么这个方法必须return response
return response
class Md2(MiddlewareMixin):
def process_request(self, request):
print('MD2--process_request')
def process_response(self, request, response):
print('Md2--响应')
# if response.content != b'ok':
# 如果process_response方法里面return了一个HttpResponse对象,这个对象会覆盖视图返回来的HttpResponse对象
# return HttpResponse('响应不ok!!!')
return response
注意:当中间件1的process_request方法return的是一个HttpResponse对象,那么会执行中间件1的process_response方法,不会去执行中间件2 的方法了
----------------------------------------------------------------
3 process_view(self, request, view_func, view_args, view_kwargs):
示例代码
from django.shortcuts import render, redirect, HttpResponse
from django.utils.deprecation import MiddlewareMixin
# 登录认证中间件
class LoginAuth(MiddlewareMixin):
# /login/登录请求,应该正常放行,让他能获得login.html页面
# 白名单、黑名单
white_list = ['/login/', ]
def process_request(self,request):
print('请求来啦!!!')
# 获取当前请求路径:request.path /home/
# 如果当前请求的路径在白名单里面,我们不进行登录认证
if not request.path in self.white_list:
is_login = request.session.get('is_login') #True
if not is_login:
return redirect('/login/')
# True,None
# if is_login:
# return None
# else:
# return redirect('/login/')
class Md1(MiddlewareMixin):
def process_request(self,request):
print('MD1--process_request')
# return HttpResponse('中间件md1的逻辑,没有通过!!!!')
def process_response(self, request, response):
print('Md1--响应')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('md1---view')
class Md2(MiddlewareMixin):
def process_request(self, request):
print('MD2--process_request')
def process_response(self, request, response):
print('Md2--响应')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
'''
:param request:
:param view_func: 此次请求要执行的视图函数对象
:param view_args: 要函数的参数
:param view_kwargs: 要函数的参数
:return:
'''
print('md2---view')
# print(view_func, view_args, view_kwargs)
print(view_func.__name__) # home
----------------------------------------------------------------
4 process_exception(self, request, exception)
他是当试图函数出现错误或者异常时,自动触发执行的方法,能够捕获试图出现的错误,进行一些错误的统一处理
示例代码
from django.shortcuts import render, redirect, HttpResponse
from django.utils.deprecation import MiddlewareMixin
# 登录认证中间件
class LoginAuth(MiddlewareMixin):
# /login/登录请求,应该正常放行,让他能获得login.html页面
# 白名单、黑名单
white_list = ['/login/', ]
def process_request(self,request):
print('请求来啦!!!')
# 获取当前请求路径:request.path /home/
# 如果当前请求的路径在白名单里面,我们不进行登录认证
if not request.path in self.white_list:
is_login = request.session.get('is_login') #True
if not is_login:
return redirect('/login/')
# True,None
# if is_login:
# return None
# else:
# return redirect('/login/')
class Md1(MiddlewareMixin):
def process_request(self,request):
print('MD1--process_request')
# return HttpResponse('中间件md1的逻辑,没有通过!!!!')
def process_response(self, request, response):
print('Md1--响应')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('md1---view')
def process_exception(self, request, exception):
print('md1--exception')
class Md2(MiddlewareMixin):
def process_request(self, request):
print('MD2--process_request')
def process_response(self, request, response):
print('Md2--响应')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
'''
:param request:
:param view_func: 此次请求要执行的视图函数对象
:param view_args: 要函数的参数
:param view_kwargs: 要函数的参数
:return:
'''
print('md2---view')
# print(view_func, view_args, view_kwargs)
# print(view_func.__name__) # home
def process_exception(self, request, exception):
from django.core.exceptions import PermissionDenied
# print('>>>>>>', exception, type(exception))
# if isinstance(exception,PermissionDenied):
# return HttpResponse('权限不够!!!!!')
#>>>>>> 权限不够
# >>>>>> 出错啦!!!
print('md2--exception')
试图函数示例
from django.core.exceptions import PermissionDenied
def home(request):
print('home')
# raise Exception('出错啦!!!')
raise PermissionDenied('权限不够')
return render(request, 'home.html')
----------------------------------------------------------------
5 process_template_response(self,request,response)
代码示例
from django.shortcuts import render, redirect, HttpResponse
from django.utils.deprecation import MiddlewareMixin
class Md1(MiddlewareMixin):
def process_request(self,request):
print('MD1--process_request')
# return HttpResponse('中间件md1的逻辑,没有通过!!!!')
def process_response(self, request, response):
print('Md1--响应')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('md1---view')
def process_exception(self, request, exception):
print('md1--exception')
def process_template_response(self, request, response):
print("MD1 中的process_template_response")
return response
class Md2(MiddlewareMixin):
def process_request(self, request):
print('MD2--process_request')
def process_response(self, request, response):
print('Md2--响应')
return response
def process_view(self, request, view_func, view_args, view_kwargs):
print('md2---view')
def process_exception(self, request, exception):
print('md2--exception')
def process_template_response(self, request, response):
print("MD2 中的process_template_response")
return response
试图代码
def home(request):
print('home')
def render():
print('你好render')
return HttpResponse('你好response')
ret = HttpResponse('ojbk')
# 必须给HttpResponse对象封装一个render属性等于一个render函数,才能出发中间件里面的process_template_response方法
ret.render = render
return ret
路由分发 include和命名空间 namespace
1 创建app
python manage.py startapp app02
2 配置app,在settings.py文件中
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'app01.apps.App01Config',
'app02'
]
1 在项目主目录下的urls.py(以后我们称之为总路由)文件中,写上如下内容
from django.conf.urls import url, include
from django.contrib import admin
# 路由分发功能
urlpatterns = [
url(r'^admin/', admin.site.urls),
# /app01/index/ -- 去掉/app01/ -- index/拿到app01应用下面的urls.py文件中进行路径匹配
url(r'^app01/', include('app01.urls')),
# 当访问路径是以/app01/开头的,那么django主目录下面的urls.py文件会自动帮我们去找app01下面的urls.py文件进行路径匹配
url(r'^app02/', include('app02.urls')), #include参数格式 '应用名称.urls'
]
2 在每个应用文件夹下面创建一个叫做urls.py的文件,里面写上如下内容
from django.conf.urls import url, include
from app01 import views
# 路由分发功能
urlpatterns = [
#' index/'
# 写上自己应用中的每个路径对应的函数
url(r'^index/', views.index),
]
出现一个问题,当两个应用中的某个url的别名相同了,那么使用url反向解析的时候,发现都是解析出来的都是最后一个应用的对应路径,这叫做别名冲突。
看示例:
app01下的urls.py
from django.conf.urls import url, include
from app01 import views
# 路由分发功能
urlpatterns = [
url(r'^index/', views.index),
url(r'^home/', views.home, name='home'),
]
App02下的urls.py
from django.conf.urls import url, include
from app02 import views
# 路由分发功能
urlpatterns = [
url(r'^index/', views.index),
url(r'^home/', views.home, name='home'),
]
App01的views.py文件中进行别名发现解析,代码:
def index(request):
print('app01>>>',reverse('home'))
#app01>>> /app02/home/
return HttpResponse('app01-index')
App02的views.py文件中进行别名发现解析,代码:
def index(request):
print('app02>>>',reverse('home'))
#app02>>> /app02/home/
return HttpResponse('app02-index')
发现别名冲突,导致反向解析出错了。
解决方法,命名空间
示例:总路由写法
from django.conf.urls import url, include
from django.contrib import admin
# 路由分发功能
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 路由分发时,给每个应用的路由进行了空间划分,使用namespace
url(r'^app01/', include('app01.urls', namespace='app01')),
url(r'^app02/', include('app02.urls', namespace='app02')),
]
views.py文件写法
def index(request):
# 在进行反向解析时,需要用到命名空间:别名,来进行解析
print('app02>>>',reverse('app02:home'))
#app02>>> /app02/home/
return HttpResponse('app02-index')
进行数据校验
form组件和modelform组件就是让我们的数据校验过程更加简单一些,并且他俩提供的功能非常强大
提供了三个功能
1 能够帮我们生成HTML标签
2 标签中保留之前用户输入的数据
3 数据校验
1 创建一个自定义的form类,代码如下
from django import forms
class RegisterForm(forms.Form):
phone = forms.CharField()
username = forms.CharField()
password = forms.CharField()
# 可以将label显示为中文
phone = forms.CharField(label='手机号')
username = forms.CharField(label='用户名')
password = forms.CharField(label='密码')
2 视图函数
def register(request):
if request.method == 'GET':
form = RegisterForm() #将上面的form类进行实例化
return render(request, 'register.html', {'form': form})
else:
print(request.POST)
return HttpResponse('ok')
3 创建一个html文件,比如叫做register.html,内容如下
Title
注册页面
示例:
forms组件代码
class RegisterForm(forms.Form):
# 每个字段,默认都有一个required=True的参数,表示该字段数据不能为空
# phone = forms.CharField(label='手机号', required=True)
phone = forms.CharField(
label='手机号',
required=True,
# 错误提示信息的自定制
error_messages={
'required': '小敏敏提示您,不能为空!',
}
)
username = forms.CharField(label='用户名')
password = forms.CharField(label='密码')
views.py内容如下
def register(request):
if request.method == 'GET':
form = RegisterForm()
return render(request, 'register.html', {'form': form})
else:
print(request.POST)
form = RegisterForm(data=request.POST)
# 把数据交给了RegisterForm,那么在通过form来渲染标签时,就将原来的数据以默认值的形式(value='值')生成在了对应标签上
if form.is_valid(): # 执行校验,如果所有数据都校验通过了,返回True,但凡有一个数据,没有通过校验,返回False
print('通过校验的数据', form.cleaned_data)
return HttpResponse('ok')
print('错误信息>>>',form.errors)
return render(request, 'register.html', {'form': form})
register.html内容
Title
注册页面
initial
初始值
username = forms.CharField(label='用户名',initial='张三')
# 生成input标签有个默认值
widget
插件的意思,能够定制我们的标签显示效果
示例1 密文输入框
password = forms.CharField(
label='密码',
# type = 'password'
# widget=forms.widgets.PasswordInput,
# 简写,不需要写widgets.
widget=forms.PasswordInput,
# CharField默认的插件是TextInput
# widget=forms.widgets.TextInput,
)
示例2 给标签加上一些其他属性
password = forms.CharField(
label='密码',
#attrs={'标签属性':'属性值'}
#widget=forms.TextInput(attrs={'class':'c1 c2',})
#widget=forms.PasswordInput(attrs={'class':'c1',})
widget=forms.TextInput(attrs={'class':'c1 c2','placeholder':'请输入密码'}),
)
生成单选下拉框
#sex = forms.fields.ChoiceField() fields可以不用写
sex = forms.ChoiceField(
choices=((1,'女'), (2,'男')),
#(1,'女') 生成一个option标签,value值为1,文本内容为女
label='性别',
# initial=2, #初始值
)
单选radio框
sex = forms.ChoiceField(
choices=((1,'女'), (2,'男')),
label='性别',
# initial=2,
# widget=forms.Select, #ChoiceField的默认插件
# input,type='radio'的单选框
# widget=forms.RadioSelect,
widget=forms.RadioSelect(attrs={'class': 'c1'}),
)
多选下拉框
hobby = forms.MultipleChoiceField(
choices=((1,'喝酒'),(2,'抽烟'),(3,'励志')),
label='爱好',
)
多选checkbox框
hobby = forms.MultipleChoiceField(
choices=((1, '喝酒'), (2, '抽烟'), (3, '励志')),
label='爱好',
# widget=forms.CheckboxInput, #做单选功能的复(多)选框形式
widget=forms.CheckboxSelectMultiple,
)
单选功能的复选框形式
hobby = forms.MultipleChoiceField(
#choices=((1, '喝酒'), (2, '抽烟'), (3, '励志')),
label='爱好',
widget=forms.CheckboxInput, #做单选功能的复(多)选框形式
)
date类型
bday = forms.CharField(
label='生日',
widget=forms.TextInput(attrs={'type': 'date'}),
)
方式1
models中的模型类
class Publish(models.Model):
name = models.CharField(max_length=32)
def __str__(self):
return self.name
form类中的字段写法
# 生成选择框,并使用数据库中的数据
publish = forms.ModelChoiceField(
queryset=models.Publish.objects.all(),
)
会自动生成单选下拉框,并且option标签value属性对应的值,是queryset参数对应的queryset集合里面的models模型类对象的id值,option标签的文本内容是每个models模型类对象
方式2
publish = forms.ChoiceField(
choices=models.Publish.objects.all().values_list('id','name') #queryset[(1,xasdf),]
)
看示例
Field
required=True, 是否允许为空
widget=None, HTML插件
label=None, 用于生成Label标签或显示内容
initial=None, 初始值
help_text='', 帮助信息(在标签旁边显示)
# {
{ form.字段名称.help_text }}
error_messages=None, 错误信息 {'required': '不能为空', 'invalid': '格式错误'}
validators=[], 自定义验证规则
disabled=False, 是否可以编辑
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类型
复制代码
格式不对的错误信息定制
'invalid': '邮箱格式不对',
form类
class DataForm(forms.Form):
# 不能为空,长度不能超过10位,不能短于4位
username = forms.CharField(
label='用户名',
max_length=10,
min_length=4,
error_messages={
'required':'不能为空',
'max_length':'太长啦,受不了',
'min_length':'太短啦,不舒服',
}
)
# 可以为空
phone = forms.CharField(
label='手机号',
required=False,
)
# 要满足邮箱格式,不能为空
email = forms.EmailField( #input,type='email'和input,type='text'是一样的,都是普通文本输入框
label='邮箱',
error_messages={
'invalid': '邮箱格式不对',
}
)
# 提交的数据不能超出选项
sex = forms.ChoiceField(
label='性别',
choices=((1,'女'),(2,'男')),
widget=forms.RadioSelect,
)
#form只校验类中写的属性对应的那些数据
# {'xx':'oo'}
# csrfmiddlewaretoken = forms.CharField()
view.py
def data(request):
if request.method == 'GET':
form = DataForm()
return render(request,'data.html',{'form': form})
else:
form = DataForm(data=request.POST)
if form.is_valid():
# 校验成功之后的数据form.cleaned_data,里面不包含没有校验的数据,也就是不会校验form类中没有指定的属性对应的数据
print(form.cleaned_data)
# {'username': 'chao', 'phone': '', 'email': '[email protected]', 'sex': '1'}
# todo 保存数据
return HttpResponse('ok')
else:
#print(form.errors)
return render(request,'data.html',{'form': form})
Data.html文件
Title
示例代码
from django.core.validators import RegexValidator
class DataForm(forms.Form):
# 不能为空,长度不能超过10位,不能短于4位
username = forms.CharField(
....
# RegexValidator('正则','不符合规则时的错误信息')
validators=[RegexValidator(r'^a', '用户名必须以a开头'),RegexValidator(r'b$', '用户名必须以b结尾')],
示例
import re
from django.core.validators import RegexValidator
from django.core.exceptions import ValidationError
# ValidationError是django提供的一个数据检验失败的一个错误类型
# 先定义一个函数,函数中我们可以做数据的校验,如果数据校验失败,我们可以raise ValidationError('错误信息')
def mobile_match(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('手机号格式有误!')
使用
phone = forms.CharField(
label='手机号',
# required=False,
validators=[mobile_match, ],
)
代码示例
class DataForm(forms.Form):
# 不能为空,长度不能超过10位,不能短于4位
username = forms.CharField(
label='用户名',
max_length=10,
min_length=4,
)
# 可以为空
phone = forms.CharField(
validators=[mobile_match, ],
)
...
# 局部钩子,能够对username这个字段的数据进行一个更高级的校验
# 语法 clean_字段名称
def clean_username(self):
uname = self.cleaned_data.get('username')
if '666' in uname:
raise ValidationError('不能光喊6,没有用!')
# 如果校验通过,需要return这个值
return uname
def clean_phone(self):
pass
示例代码
class DataForm(forms.Form):
# 密码
password = forms.CharField()
# 确认密码
confirm_password = forms.CharField()
# 多个字段进行比较时,一般都是用全局钩子
def clean(self):
p1 = self.cleaned_data.get('password')
p2 = self.cleaned_data.get('confirm_password')
if p1 != p2:
# 直接raise错误,这个错误信息保存到了全局错误信息里面
# 也就是self.errors里面
# raise ValidationError('两次密码不一致,你恐怕是个sz吧')
self.add_error('confirm_password', '抱歉,两次密码不一致,请核对!')
self.add_error('password', '抱歉,两次密码不一致,请核对!')
# 如果校验通过,必须return self.cleaned_data
return self.cleaned_data
def _clean_fields(self):
for name, field in self.fields.items():
# print(name, field)
# name属性名称,field是CharField()类的实例化对象
# 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))
try:
if isinstance(field, FileField):
initial = self.get_initial_for_field(field, name)
value = field.clean(value, initial)
else:
# 将CharField中的max_length,required..
value = field.clean(value)
# 将属性对应的CharField里面的简单校验规则进行了校验
self.cleaned_data[name] = value
#self.cleaned_data['username'] = 'abc'
if hasattr(self, 'clean_%s' % name): #clean_username
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self.add_error(name, e)
首先对所有的我们自己定义的类中的属性进行了循环,self.fields能够获取到所有的属性
循环过程中先对属性,比如username = form.Charfield(max_length=12),里面的参数校验规则进行了校验,这个校验完成之后。给self.clean_data这个字典进行了赋值self.clean_data['username'] = 'asdf'
然后对该字段的局部钩子进行了校验,在局部钩子中我们可以拿到self.clean_data['username'] 这个数据进行处理,别忘了局部钩子校验成功之后,要return 这个字段的值,然后才进入下一次循环,处理下一个属性
当上面的所有循环执行完之后,才执行我们的全局够子,全局钩子中校验成功之后别忘了return self.clean_data
可以在应用文件夹下创建一个static名称的文件夹,存放自己应用的静态文件
可以在应用文件夹下创建一个templates名称的文件夹,存放自己应用的html文件
django寻找html文件静态文件的查找顺序是
先找总配置中的templates或者statics文件夹,如果找不到对应文件,就去每个应用下的static或者templates去找对应文件,顺序是按照settings.py配置文件中的INDSTALL_APP中注册app的顺序进行查找,找到就返回,不在继续往下找了,所以要注意,最好在static或者templates下面创建一个以应用名为名称的文件夹,将文件放到这里面,以防文件名冲突导致的问题。
1 自定义modelform的类
class BookModelForm(forms.ModelForm):
class Meta:
model = models.Book
fields = '__all__'
labels = {
'title': '书籍名称',
'pub': '出版社',
'price': '价格',
'pub_date': '出版日期',
'authors': '作者',
}
error_messages = {
'title': {'required': '不能为空', 'max_length': '不能太长,受不了'},
'price': {'required': '不能为空', },
}
widgets = {
'pub_date': forms.TextInput(attrs={'type':"date"}),
'title': forms.TextInput(attrs={'class':"c1"}),
}
# 想在自己定义的forms类在初始化的时候统一加一些样式
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for name, field in self.fields.items():
# print(name, field)
field.widget.attrs.update({'class': 'form-control'})
2 在视图中使用modelform
class Edit(View):
def get(self, request, pk):
old_book_obj = models.Book.objects.get(pk=pk)
# 如果不传instance=old_book_obj, 那么就是一个添加页面效果,也就是生成标签没有默认值
# form = BookModelForm()
form = BookModelForm(instance=old_book_obj)
return render(request, 'edit.html',{'form': form,})
def post(self,request,pk):
old_book_obj = models.Book.objects.filter(pk=pk).first()
# 如果没有传instance=old_book_obj,那么form.save()做的是添加记录的动作,
# 如果传了instance=old_book_obj,那么form.save()就是一个修改动作
form = BookModelForm(data=request.POST, instance=old_book_obj) #
if form.is_valid():
# authors_objs = form.cleaned_data.pop('authors')
#
# book_obj = models.Book.objects.create(
# **form.cleaned_data
# )
# book_obj.authors.add(*authors_objs)
form.save()
return redirect('book_list')
else:
return render(request, 'edit.html',{'form': form,})
3 html中使用modelform
pub = models.ForeignKey('Publish', verbose_name='出版社')
对应modelform中的label定制