一、Django简介
1、MVC模型
MVC模型是web服务器开发领域著名的模型。
M:模型,业务对象与数据库的映射(ORM)
V:视图,用于与用户的交互(页面)
C:控制器,接收用户的输入调用模型和视图完成用户的请求
2、MTV模型
MTV模型是Django的web服务器开发模型,与MVC本质上的原理是一样的,也是为了各组件间保持松耦合关系。
M(model):模型,负责业务对象与数据库的关系映射(ORM)
T(template):模板,负责将HTML页面展示给用户
V(view):视图,负责业务逻辑,并在适当时候调用Model和Template
除了以上三层之外,还需要一个URL分发器,它的作用是将一个个URL的页面请求分发给不同的View处理,View再调用相应的Model和Template,最后将响应数据返回给用户。
3、Django的下载
pip3 install django # 下载最新版本 pip3 install django==1.11 # 指定版本下载
4、创建一个Django项目:
django-admin.py startproject mysite # 创建一个叫mysite的Django项目
当前目录下会生成mysite的工程,目录结构如下:
manage.py ----- Django项目里面的工具,通过它可以调用django shell和数据库等。
settings.py ---- 包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量。
urls.py ----- 负责把URL模式映射到应用程序。
wsgi.py----遵循wsgi规范的模块
5、在mysite目录下创建应用
python3 manage.py startapp blog # 创建一个叫blog的app
目录结构:
6、启动Django项目
python3 manage.py runserver 127.0.0.1:8088
7、基于Django实现一个简单的示例:显示当前日期
import time from django.shortcuts import render def index(request): ctime = time.strftime("%Y-%m-%d %X") return render(request, "index.html", {"ctime": ctime})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>显示当前时间</title> </head> <body> <div> <h3>{{ ctime }}</h3> </div> </body> </html>
from django.contrib import admin from django.urls import path from blog import views urlpatterns = [ path('admin/', admin.site.urls), path('index/', views.index), # 设置路由 ]
记得在settings.py中设置templates的路径:
在浏览器访问http://127.0.0.1:8088/index/即可:
二、Django的路由
1、使用正则
通过"圆括号"来捕获URL 中的值并以位置参数传递给视图。
from django.contrib import admin from django.urls import path, re_path from blog import views urlpatterns = [ # http://127.0.0.1:8088/articles/2003/ 将调用函数special_case_2003(request) re_path(r'^articles/2003/$', views.special_case_2003), # http://127.0.0.1:8088/articles/2004/ 将调用函数year_archive(request, year) re_path(r'^articles/([0-9]{4})/$', views.year_archive), # /articles/2005/03/ 将调用函数month_archive(request, year, month) re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), # http://127.0.0.1:8088/articles/2003/04/12/ 将调用函数article_detail(request, year, month, day) re_path(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail), ]
from django.shortcuts import render, HttpResponse def special_case_2003(request): return HttpResponse("special_case_2003") def year_archive(request, year): print(year) return HttpResponse("year_archive") def month_archive(request, year, month): print(year, month) return HttpResponse("month_archive") def article_detail(request, year, month, day): print(year, month, day) return HttpResponse("article_detail")
2、有名分组
给URL中的参数命名,语法:(?P<name>pattern),name是指组的名称, pattern是指要匹配的模式。
与上面的区别在于,捕获的值作为关键字参数而不是位置参数传递给视图函数。
from django.contrib import admin from django.urls import path, re_path from blog import views urlpatterns = [ # http://127.0.0.1:8088/articles/2003/ 将调用函数special_case_2003(request) re_path(r'^articles/2003/$', views.special_case_2003), # http://127.0.0.1:8088/articles/2004/ 将调用函数year_archive(request, year) re_path(r'^articles/(?P<year>[0-9]{4})/$', views.year_archive), # /articles/2005/03/ 将调用函数month_archive(request, year, month) re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive), # http://127.0.0.1:8088/articles/2003/04/12/ 将调用函数article_detail(request, year, month, day) re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/(?P<day>[0-9]+)/$', views.article_detail), ]
3、路由分发
有时候在开发一个项目中,可以会有多个APP,我们可以在每个APP下单独给这个APP设置urls,使得我们能够更清晰直观的读取URL。
如:
在blog app下新建urls.py:
from django.urls import path from blog import views urlpatterns = [ path('index/', views.index), ]
在全局urls.py中通过include来进行路由分发:
from django.contrib import admin from django.urls import path, re_path, include from blog import views urlpatterns = [ path('admin/', admin.site.urls), path('blog/', include("blog.urls")), # http://127.0.0.1:8088/blog/index/ 将访问blog下的urls.py ]
import time from django.shortcuts import render, HttpResponse def index(request): ctime = time.strftime("%Y-%m-%d %X") return render(request, "index.html", {"ctime": ctime})
4、反向解析
人们强烈希望不要硬编码这些URL,费力、不可扩展且容易产生错误,通过给URL设置name,再借助Django 提供不同的工具用于URL 反查
(1)在模板中:使用url 模板标签 {% %}
(2)在视图中:使用from django.urls import reverse 函数
from django.contrib import admin from django.urls import path, re_path, include from blog import views urlpatterns = [ path('admin/', admin.site.urls), re_path(r'^book/(?P<pk>\d+)/$', views.book, name="book"), ]
from django.shortcuts import render, HttpResponse from django.urls import reverse def book(request, pk): url = reverse("book", args=(pk,)) # 反向获取URL,book是在urls.py中给URL设置的名字,args是URL中的参数 print(url) return render(request, "book.html", locals())
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {# book是给URL设置的名称,pk是URL中的参数 #} <a href="{% url "book" pk %}">book {{ pk }}</a> </body> </html>
5、命名空间
命名空间(Namespace)是表示标识符的可见范围。
由于name没有作用域,Django在反解URL时,会在项目全局顺序搜索,当查找到第一个name指定URL时,立即返回。 我们在开发项目时,会经常使用name属性反解出URL,当不小心在不同的app的urls中定义相同的name时,可能会导致URL反解错误,为了避免这种事情发生,引入了命名空间。
在Django项目mysite下新建两个APP:
python3 manage.py startapp app01
python3 manage.py startapp app02
mysite/urls.py:
from django.urls import path, re_path, include urlpatterns = [ re_path(r'^app01/', include(("app01.urls", "app01"), namespace="app01")), re_path(r'^app02/', include(("app02.urls", "app02"), namespace="app02")), ]
app01/urls.py:
from django.conf.urls import re_path from app01.views import index urlpatterns = [ re_path(r'^index/$', index, name="index"), ]
app02/urls.py:
from django.conf.urls import re_path from app02.views import index urlpatterns = [ re_path(r'^index/$', index, name="index"), ]
app01/views.py:
from django.shortcuts import HttpResponse def index(request): return HttpResponse("in app01-index")
app02/views.py:
from django.shortcuts import HttpResponse def index(request): return HttpResponse("in app02-index")
通过浏览器访问:
include()源码:
6、Django2.0版本的path
有如下URL:
urlpatterns = [ re_path('articles/(?P<year>[0-9]{4})/', year_archive), re_path('article/(?P<article_id>[a-zA-Z0-9]+)/detail/', detail_view), re_path('articles/(?P<article_id>[a-zA-Z0-9]+)/edit/', edit_view), re_path('articles/(?P<article_id>[a-zA-Z0-9]+)/delete/', delete_view), ]
思考:
(1)默认函数 year_archive 中year参数是字符串类型的,有时候我们需要将它转换成整数类型的变量值来使用,当然year=int(year) 不会有诸如如TypeError或者ValueError的异常。那么有没有一种方法,在url中,使得这一转化步骤可以由Django自动完成?
(2)三个路由中article_id都是同样的正则表达式,但是你需要写三遍,当之后article_id规则改变后,需要同时修改三处代码,那么有没有一种方法,只需修改一处即可?
在Django2.0中,可以使用 path 解决以上的两个问题,基本规则:
(1)使用尖括号(<>)从url中捕获值。
(2)捕获值中可以包含一个转化器类型(converter type),比如使用<int:year>捕获一个整数变量。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符。
from django.urls import path from . import views urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<int:year>/', views.year_archive), path('articles/<int:year>/<int:month>/', views.month_archive), path('articles/<int:year>/<int:month>/<slug>/', views.article_detail), ]
Django默认支持以下5个转化器:
- str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
- int,匹配正整数,包含0。
- slug,匹配字母、数字以及横杠、下划线组成的字符串。
- uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
- path,匹配任何非空字符串,包含了路径分隔符
可以注册自定义转化器
对于一些复杂或者复用的需要,可以定义自己的转化器。转化器是一个类或接口,它的要求有三点:
- regex 类属性,字符串类型
- to_python(self, value) 方法,value是由类属性 regex 所匹配到的字符串,返回具体的Python变量值,以供Django传递到对应的视图函数中。
- to_url(self, value) 方法,和 to_python 相反,value是一个具体的Python变量值,返回其字符串,通常用于url反向引用。
class FourDigitYearConverter: regex = '[0-9]{4}' def to_python(self, value): return int(value) def to_url(self, value): return '%04d' % value
使用register_converter 将其注册到URL配置中:
from django.urls import register_converter, path from . import converters, views
# 注册类,并给这个转换器命名为xx register_converter(converters.FourDigitYearConverter, 'xx') urlpatterns = [ path('articles/2003/', views.special_case_2003), path('articles/<xx:year>/', views.year_archive), # 使用转换器 ]
三、Django的视图
Django中的视图有两类:基于函数的视图(FBV)和基于类的视图(CBV),它接收Web请求(HttpRequest对象
)并且返回Web响应(HttpResponse对象)。
1、HttpRequest对象
1.HttpRequest.GET 一个类似于字典的QueryDict对象,包含了 HTTP GET 的所有参数。 2.HttpRequest.POST 一个QueryDict 对象,如果请求中包含表单数据,则将这些数据封装成 QueryDict 对象。 POST 请求可以带有空的POST字典。如果通过POST方法发送一个表单,但是表单中没有任何的数据,QueryDict 对象依然会被创建。 因此,不应该使用 if request.POST 来检查使用的是否是POST 方法;应该使用 if request.method == "POST" 另外:如果使用 POST 上传文件的话,文件信息将包含在 FILES 属性中。 注意:键值对的值是多个的时候,比如checkbox类型的input标签,select标签,需要用:request.POST.getlist("hobby") 来获取数据 3.HttpRequest.body 一个字符串,代表请求报文的主体。在处理非 HTTP 形式的报文时非常有用,例如:二进制图片、XML,Json等。 但是,如果要处理表单数据的时候,推荐还是使用 HttpRequest.POST 。 4.HttpRequest.path 一个字符串,表示请求的路径组件(不含域名)。 例如:"/music/bands/the_beatles/" 5.HttpRequest.method 一个字符串,表示请求使用的HTTP 方法。必须使用大写。 例如:"GET"、"POST" 6.HttpRequest.encoding 一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 'utf-8')。 这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。 接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。 如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。 7.HttpRequest.META 一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器,下面是一些示例: CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。 CONTENT_TYPE —— 请求的正文的MIME 类型。 HTTP_ACCEPT —— 响应可接收的Content-Type。 HTTP_ACCEPT_ENCODING —— 响应可接收的编码。 HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。 HTTP_HOST —— 客服端发送的HTTP Host 头部。 HTTP_REFERER —— Referring 页面。 HTTP_USER_AGENT —— 客户端的user-agent 字符串。 QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。 REMOTE_ADDR —— 客户端的IP 地址。 REMOTE_HOST —— 客户端的主机名。 REMOTE_USER —— 服务器认证后的用户。 REQUEST_METHOD —— 请求方法,一个字符串,例如"GET" 或"POST"。 SERVER_NAME —— 服务器的主机名。 SERVER_PORT —— 服务器的端口(是一个字符串)。 从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,请求中的任何 HTTP 首部转换为 META 的键时, 都会将所有字母大写并将连接符替换为下划线最后加上 HTTP_ 前缀。 所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。 8.HttpRequest.FILES 一个类似于字典的对象,包含所有的上传文件信息。 FILES 中的每个键为<input type="file" name="" /> 中的name,值则为对应的数据。 注意,FILES 只有在请求的方法为POST 且提交的<form> 带有enctype="multipart/form-data" 的情况下才会 包含数据。否则,FILES 将为一个空的类似于字典的对象。 9.HttpRequest.COOKIES 一个标准的Python 字典,包含所有的cookie。键和值都为字符串。 10.HttpRequest.session 一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。 11.HttpRequest.user(用户认证组件下使用) 一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。 如果用户当前没有登录,user 将设置为 django.contrib.auth.models.AnonymousUser 的一个实例。你可以通过 is_authenticated() 区分它们。 例如: if request.user.is_authenticated(): # Do something for logged-in users. else: # Do something for anonymous users. user 只有当Django 启用 AuthenticationMiddleware 中间件时才可用。 ------------------------------------------------------------------------------------- 匿名用户 class models.AnonymousUser django.contrib.auth.models.AnonymousUser 类实现了django.contrib.auth.models.User 接口,但具有下面几个不同点: id 永远为None。 username 永远为空字符串。 get_username() 永远返回空字符串。 is_staff 和 is_superuser 永远为False。 is_active 永远为 False。 groups 和 user_permissions 永远为空。 is_anonymous() 返回True 而不是False。 is_authenticated() 返回False 而不是True。 set_password()、check_password()、save() 和delete() 引发 NotImplementedError。 New in Django 1.8: 新增 AnonymousUser.get_username() 以更好地模拟 django.contrib.auth.models.User。
常用方法:
1.HttpRequest.get_full_path() 返回 path,如果可以将加上查询字符串。 例如:"/music/bands/the_beatles/?print=true" 2.HttpRequest.is_ajax() 如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串'XMLHttpRequest'。 大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。 如果一个响应需要根据请求是否是通过AJAX 发起的,并且你正在使用某种形式的缓存例如Django 的 cache middleware, 你应该使用 vary_on_headers('HTTP_X_REQUESTED_WITH') 装饰你的视图以让响应能够正确地缓存。
2、HttpResponse对象
1、响应对象主要有三种形式: HttpResponse() render() redirect() 2、HttpResponse() 直接在括号内加上字符串即可返回,如HttpResponse("操作成功!") 3、render() 格式:render(request, template_name[, context]) 结合一个给定的模板和一个给定的上下文字典,将一个模板页面中的模板语法进行渲染,最终渲染成一个html页面作为响应体。 参数: request: 用于生成响应的请求对象。 template_name:要使用的模板的完整名称 context:添加到模板上下文的一个字典。默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。 4、redirect() 传递要重定向的一个硬编码的URL 如: def my_view(request): ... return redirect('/books/2/')
301和302重定向:
(1)301和302的区别。 301和302状态码都表示重定向,就是说浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址,这个地址可以从响应的Location首部中获取 (用户看到的效果就是他输入的地址A瞬间变成了另一个地址B)——这是它们的共同点。 他们的不同在于。301表示旧地址A的资源已经被永久地移除了(这个资源不可访问了),搜索引擎在抓取新内容的同时也将旧的网址交换为重定向之后的网址; 302表示旧地址A的资源还在(仍然可以访问),这个重定向只是临时地从旧地址A跳转到地址B,搜索引擎会抓取新的内容而保存旧的网址。 (2)重定向原因: 1)网站调整(如改变网页目录结构); 2)网页被移到一个新地址; 3)网页扩展名改变(如应用需要把.php改成.Html或.shtml)。 这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息,访问流量白白丧失;再者某些注册了多个域名的网站,也需要通过重定向让访问这些域名的用户自动跳转到主站点等。
四、Django的模板
集中存放HTML代码文件的地方
1、Django模板语法——变量
在模板中调用变量:{{var_name}}
import datetime from django.shortcuts import render def index(request): s = "hello" l = [111, 222, 333] # 列表 dic = {"name": "amy", "age": 18} # 字典 date = datetime.date(1993, 5, 2) # 日期对象 class Person(object): def __init__(self, name): self.name = name person_wang = Person("wang") # 自定义类对象 person_li = Person("li") person_he = Person("he") person_list = [person_wang, person_li, person_he] return render(request, "index.html", {"s": s, "l": l, "dic": dic, "date": date, "person_list": person_list})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>模板语法之变量</title> </head> <body> <div> <h4>字符串:{{ s }}</h4> <h4>列表:{{ l.0 }}</h4> <h4>列表:{{ l.2 }}</h4> <h4>字典:{{ dic.name }}</h4> <h4>日期:{{ date.year }}</h4> <h4>类对象列表:{{ person_list.0.name }}</h4> </div> </body> </html>
2、模板语法——过滤器
语法:{{obj|filter__name:param}}
常见过滤器: 1、default 如果一个变量是false或者为空,使用给定的默认值。否则,使用变量的值。例如: {{ value|default:"nothing" }} 2、length 返回值的长度。它对字符串和列表都起作用。例如: {{ value|length }} 如果 value 是 ['a', 'b', 'c', 'd'],那么输出是 4。 3、date 如果 value=datetime.datetime.now(),想要显示“2019-1-12”的格式,则写为:{{ value|date:"Y-m-d" }} 4、slice 通过字符串的index值来截取字符串 如果 value="hello world",{{ value|slice:"2:-1" }}将会显示:llo worl 5、truncatechars 如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“...”)结尾。 参数:要截断的字符数 例如:value="hello world",{{ value|truncatechars:6 }} 结果:hel... 6、safe Django的模板中会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全。但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。比如: value="<a href="">点击</a>" {{ value|safe}}
3、模板语法——标签
语法:{% tag %}
用于一些在输出中创建文本,一些通过循环或逻辑来控制流程,一些加载其后的变量将使用到的额外信息到模版中的情况
(1)for标签:遍历每一个元素
- 遍历字典:
{% for key,val in dic.items %} <p>{{ key }}:{{ val }}</p> {% endfor %}
- 循环序号:{{forloop}}
forloop.counter # 计数,index从1开始 ,如1,2,3 forloop.counter0 # 计数,index从0开始,如 0,1,2 forloop.revcounter # 计数,从最后一个index开始倒数到1,如3,2,1 forloop.revcounter0 # 计数,从最后一个index开始倒数到0,如2,1,0 forloop.first # 判断是否为第一个元素,返回true或false forloop.last # 判断是否为最后一个元素,返回true或false # 例: {% for item in l %} <p>{{ forloop.counter }}</p> {% endfor %}
- for ... empty
for 标签带有一个可选的{% empty %}
从句,以便在给出的组是空的或者没有被找到时,可以有所操作。
{% for person in person_list %} {# 如果person_list不为空就打印这里: #} <p>{{ person.name }}</p> {% empty %} {# 如果person_list为空就打印这里: #} <p>sorry,no person here</p> {% endfor %}
(2)if 标签{% if %}
会对一个变量求值,如果它的值是“True”(存在、不为空、且不是boolean类型的false值),对应的内容块会输出。
{% if num > 100 or num < 0 %} <p>无效</p> {% elif num > 80 and num < 100 %} <p>优秀</p> {% else %} <p>凑活吧</p> {% endif %}
(3)with
使用一个简单地名字缓存一个复杂的变量,当你需要使用一个“昂贵的”方法(比如访问数据库)很多次的时候是非常有用的
{% with total=business.employees.count %}
{{ total }} employee{{ total|pluralize }}
{% endwith %}
(4)csrf_token
这个标签用于跨站请求伪造保护
<form> {% csrf_token %} <input type="text" name="username"/> <input type="submit" value="提交"/> </form>
使用这个标签后,在发送form表单数据时就不会出现404 forbidden
(5)可以利用 {% for obj in list reversed %}
反向完成循环。
4、自定义标签和过滤器
(1)在settings中的INSTALLED_APPS配置当前app,不然django无法找到自定义的simple_tag.
(2)在app中创建templatetags模块(模块名只能是templatetags)
(3)创建任意 .py 文件,如:
from django import template from django.utils.safestring import mark_safe register = template.Library() # register的名字是固定的,不可改变 @register.filter def filter_multi(v1, v2): return v1 * v2 @register.simple_tag def simple_tag_multi(v1, v2): return v1 * v2 @register.simple_tag def my_input(id, arg): result = "<input type='text' id='%s' class='%s' />" % (id, arg,) return mark_safe(result)
(4)在使用自定义simple_tag和filter的html文件中导入之前创建的 my_tags.py
{% load my_tags %}
(5)使用simple_tag和filter
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>xxx</title> </head> <body> <div> {% load my_tags %} {# s是视图中的变量,s=10 #} {{ s|filter_multi:2 }} {{ 3|filter_multi:3 }} {% simple_tag_multi 2 7 %} {# 参数不限,但不能放在if、for语句中 #} {% simple_tag_multi s 5 %} {% my_input s "username" %} </div> </body> </html>
结果:
注意:filter可以用在if等语句后,simple_tag不可以
{% if num|filter_multi:30 > 100 %}
{{ num|filter_multi:30 }}
{% endif %}
5、模板继承
模版继承可以让您创建一个基本的“骨架”模版,它包含您站点中的全部元素,并且可以定义能够被子模版覆盖的 blocks 。
语法:{% block name %} content {% endblock %}
name表示block的名称,content表示内容
新建一个base.html的文档作为HTML基本骨架:
<!DOCTYPE html> <html lang="en"> <head> <title> {% block title %}My amazing site{% endblock %} </title> </head> <body> <div id="sidebar"> {% block sidebar %} <ul> <li><a href="/">Home</a></li> <li><a href="/blog/index/">Blog</a></li> </ul> {% endblock %} </div> <div id="content"> {% block content %}{% endblock %} </div> </body> </html>
在这个例子中, block 标签定义了三个可以被子模版内容填充的block。
导入和使用模板:
{% extends "base.html" %} {% block title %}this is index{% endblock %} {% block content %} <h4>in content block</h4> {% endblock %}
注意:
- 如果你在模版中使用
{% extends %}
标签,它必须是模版中的第一个标签。其他的任何情况下,模版继承都将无法工作。 - 在base模版中设置越多的
{% block %}
标签越好。请记住,子模版不必定义全部父模版中的blocks,所以,你可以在大多数blocks中填充合理的默认内容,然后,只定义你需要的那一个。多一点钩子总比少一点好。 - 如果你发现你自己在大量的模版中复制内容,那可能意味着你应该把内容移动到父模版中的一个
{% block %}
中。 - If you need to get the content of the block from the parent template, the variable will do the trick. This is useful if you want to add to the contents of a parent block instead of completely overriding it. Data inserted using will not be automatically escaped (see the next section), since it was already escaped, if necessary, in the parent template.
- 为了更好的可读性,你也可以给你的
{% endblock %}
标签一个 名字 。例如:
{% block content %}
...
{% endblock content %}
在大型模版中,这个方法帮你清楚的看到哪一个 {% block %}
标签被关闭了。
- 不能在一个模版中定义多个相同名字的 block 标签。
五、Django模型层
MVC或者MTV框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动
ORM是“对象-关系-映射”的简称。
1、mysql操作和Python类操作数据库
MySQL:
# 创建数据库 CREATE DATABASE db4 default character set utf8 collate utf8_general_ci; # 创建数据表 CREATE TABLE employee(id INT PRIMARY KEY auto_increment , name VARCHAR (20), gender BIT default 1, birthday DATE , department VARCHAR (20), salary DECIMAL (8,2) unsigned); # 插入数据 INSERT employee (name,gender,birthday,salary,department) VALUES ("amy",1,"1999-11-11",8000,"开发部"); # 查询 SELECT * FROM employee WHERE gender=1; # 修改 UPDATE employee SET birthday="1993-10-24" WHERE id=1; # 删除 DELETE FROM employee WHERE name="amy";
Python类:
新建一个Django工程。
编写类:
from django.db import models class Employee(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=32) gender = models.BooleanField() birthday = models.DateField() department = models.CharField(max_length=32) salary = models.DecimalField(max_digits=8, decimal_places=2)
在terminal中做数据库迁移:
python3 manage.py makemigrations
python3 manage.py migrate
编写视图:
from django.shortcuts import render, HttpResponse from app01.models import Employee def index(request): # 添加一条表纪录(实例化类): emp = Employee(name="amy", gender=True, birthday="1999-12-12", department="开发部", salary=8000) emp.save() # 查询一条表纪录: Employee.objects.filter(gender=True) # 更新一条表纪录: Employee.objects.filter(id=1).update(birthday="1999-10-24") # # 删除一条表纪录: Employee.objects.filter(name="amy").delete() return HttpResponse(".....")
编写路由:
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), ]
2、单表操作
(1)创建Django项目,创建名为app01的app,在app01的models.py中创建模型:
from django.db import models class Book(models.Model): id = models.AutoField(primary_key=True) title = models.CharField(max_length=32) state = models.BooleanField() pub_date = models.DateField() price = models.DecimalField(max_digits=8, decimal_places=2) publish = models.CharField(max_length=32)
字段:
<1> CharField 字符串字段, 用于较短的字符串. CharField 要求必须有一个参数 maxlength, 用于从数据库层和Django校验层限制该字段所允许的最大字符数. <2> IntegerField #用于保存一个整数. <3> FloatField 一个浮点数. 必须 提供两个参数: 参数 描述 max_digits 总位数(不包括小数点和符号) decimal_places 小数位数 举例来说, 要保存最大值为 999 (小数点后保存2位),你要这样定义字段: models.FloatField(..., max_digits=5, decimal_places=2) 要保存最大值一百万(小数点后保存10位)的话,你要这样定义: models.FloatField(..., max_digits=19, decimal_places=10) admin 用一个文本框(<input type="text">)表示该字段保存的数据. <4> AutoField 一个 IntegerField, 添加记录时它会自动增长. 你通常不需要直接使用这个字段; 自定义一个主键:my_id=models.AutoField(primary_key=True) 如果你不指定主键的话,系统会自动添加一个主键字段到你的 model. <5> BooleanField A true/false field. admin 用 checkbox 来表示此类字段. <6> TextField 一个容量很大的文本字段. admin 用一个 <textarea> (文本区域)表示该字段数据.(一个多行编辑框). <7> EmailField 一个带有检查Email合法性的 CharField,不接受 maxlength 参数. <8> DateField 一个日期字段. 共有下列额外的可选参数: Argument 描述 auto_now 当对象被保存时,自动将该字段的值设置为当前时间.通常用于表示 "last-modified" 时间戳. auto_now_add 当对象首次被创建时,自动将该字段的值设置为当前时间.通常用于表示对象创建时间. (仅仅在admin中有意义...) <9> DateTimeField 一个日期时间字段. 类似 DateField 支持同样的附加选项. <10> ImageField 类似 FileField, 不过要校验上传对象是否是一个合法图片.#它有两个可选参数:height_field和width_field, 如果提供这两个参数,则图片将按提供的高度和宽度规格保存. <11> FileField 一个文件上传字段. 要求一个必须有的参数: upload_to, 一个用于保存上载文件的本地文件系统路径. 这个路径必须包含 strftime #formatting, 该格式将被上载文件的 date/time 替换(so that uploaded files don't fill up the given directory). admin 用一个<input type="file">部件表示该字段保存的数据(一个文件上传部件) . 注意:在一个 model 中使用 FileField 或 ImageField 需要以下步骤: (1)在你的 settings 文件中, 定义一个完整路径给 MEDIA_ROOT 以便让 Django在此处保存上传文件. (出于性能考虑,这些文件并不保存到数据库.) 定义MEDIA_URL 作为该目录的公共 URL. 要确保该目录对 WEB服务器用户帐号是可写的. (2) 在你的 model 中添加 FileField 或 ImageField, 并确保定义了 upload_to 选项,以告诉 Django 使用 MEDIA_ROOT 的哪个子目录保存上传文件.你的数据库中要保存的只是文件的路径(相对于 MEDIA_ROOT). 出于习惯你一定很想使用 Django 提供的 get_<#fieldname>_url 函数.举例来说,如果你的 ImageField 叫作 mug_shot, 你就可以在模板中以 {{ object.#get_mug_shot_url }} 这样的方式得到图像的绝对路径. <12> URLField 用于保存 URL. 若 verify_exists 参数为 True (默认), 给定的 URL 会预先检查是否存在( 即URL是否被有效装入且 没有返回404响应). admin 用一个 <input type="text"> 文本框表示该字段保存的数据(一个单行编辑框) <13> NullBooleanField 类似 BooleanField, 不过允许 NULL 作为其中一个选项. 推荐使用这个字段而不要用 BooleanField 加 null=True 选项 admin 用一个选择框 <select> (三个可选择的值: "Unknown", "Yes" 和 "No" ) 来表示这种字段数据. <14> SlugField "Slug" 是一个报纸术语. slug 是某个东西的小小标记(短签), 只包含字母,数字,下划线和连字符.#它们通常用于URLs 若你使用 Django 开发版本,你可以指定 maxlength. 若 maxlength 未指定, Django 会使用默认长度: 50. #在 以前的 Django 版本,没有任何办法改变50 这个长度. 这暗示了 db_index=True. 它接受一个额外的参数: prepopulate_from, which is a list of fields from which to auto-#populate the slug, via JavaScript,in the object's admin form: models.SlugField (prepopulate_from=("pre_name", "name"))prepopulate_from 不接受 DateTimeFields. <13> XMLField 一个校验值是否为合法XML的 TextField,必须提供参数: schema_path, 它是一个用来校验文本的 RelaxNG schema #的文件系统路径. <14> FilePathField 可选项目为某个特定目录下的文件名. 支持三个特殊的参数, 其中第一个是必须提供的. 参数 描述 path 必需参数. 一个目录的绝对文件系统路径. FilePathField 据此得到可选项目. Example: "/home/images". match 可选参数. 一个正则表达式, 作为一个字符串, FilePathField 将使用它过滤文件名. 注意这个正则表达式只会应用到 base filename 而不是 路径全名. Example: "foo.*\.txt^", 将匹配文件 foo23.txt 却不匹配 bar.txt 或 foo23.gif. recursive可选参数.要么 True 要么 False. 默认值是 False. 是否包括 path 下面的全部子目录. 这三个参数可以同时使用. match 仅应用于 base filename, 而不是路径全名. 那么,这个例子: FilePathField(path="/home/images", match="foo.*", recursive=True) ...会匹配 /home/images/foo.gif 而不匹配 /home/images/foo/bar.gif <15> IPAddressField 一个字符串形式的 IP 地址, (i.e. "24.124.1.30"). <16> CommaSeparatedIntegerField 用于存放逗号分隔的整数值. 类似 CharField, 必须要有maxlength参数.
参数:
(1)null 如果为True,Django 将用NULL 来在数据库中存储空值。 默认值是 False. (1)blank 如果为True,该字段允许不填。默认为False。 要注意,这与 null 不同。null纯粹是数据库范畴的,而 blank 是数据验证范畴的。 如果一个字段的blank=True,表单的验证将允许该字段是空值。如果字段的blank=False,该字段就是必填的。 (2)default 字段的默认值。可以是一个值或者可调用对象。如果可调用 ,每有新对象被创建它都会被调用。 (3)primary_key 如果为True,那么这个字段就是模型的主键。如果你没有指定任何一个字段的primary_key=True, Django 就会自动添加一个IntegerField字段做为主键,所以除非你想覆盖默认的主键行为, 否则没必要设置任何一个字段的primary_key=True。 (4)unique 如果该值设置为 True, 这个数据字段的值在整张表中必须是唯一的 (5)choices 由二元组组成的一个可迭代对象(例如,列表或元组),用来给字段提供选择项。 如果设置了choices ,默认的表单将是一个选择框而不是标准的文本框,<br>而且这个选择框的选项就是choices 中的选项。
(2)settings配置
若想将模型转为mysql数据库中的表,需要在settings中配置:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'db4', # 要连接的数据库,连接前需要创建好 'USER': 'root', # 连接数据库的用户名 'PASSWORD': '123456789', # 连接数据库的密码 'HOST': '127.0.0.1', # 连接主机,默认本级 'PORT': 3306 # 端口 默认3306 } }
对于Python3 使用PyMySQL来驱动MySQL数据库,找到项目名文件下的init.py文件,在里面写入:
import pymysql pymysql.install_as_MySQLdb()
在pycharm的终端执行数据库迁移命令即可在指定的数据库中创建表 :
python3 manage.py makemigrations
python3 manage.py migrate
注意:
- 要确保配置文件中的INSTALLED_APPS中写入我们创建的app名称:
INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', "app01" ]
- 如果报错如下:
django.core.exceptions.ImproperlyConfigured: mysqlclient 1.3.3 or newer is required; you have 0.7.11.None
MySQLclient目前只支持到python3.4,因此如果使用的更高版本的python,需要修改如下:
通过查找路径C:\Programs\Python\Python36-32\Lib\site-packages\Django-2.0-py3.6.egg\django\db\backends\mysql 这个路径里的文件把:
if version < (1, 3, 3): raise ImproperlyConfigured("mysqlclient 1.3.3 or newer is required; you have %s" % Database.__version__)
注释掉 就OK了。
- 如果想打印orm转换过程中的sql,需要在settings中进行如下配置:
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)添加表记录
方法1:
book_obj = Book.objects.create(title="葵花宝典", state=True, price=100, publish="苹果出版社", pub_date="2012-12-12")
方法2:
book_obj=Book(title="python教程",state=True,price=100,publish="苹果出版社",pub_date="2012-12-12") book_obj.save()
(4)查询记录
查询API:
<1> all(): 查询所有结果 <2> filter(**kwargs): 它包含了与所给筛选条件相匹配的对象 <3> get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个, 如果符合筛选条件的对象超过一个或者没有都会抛出错误。 <4> exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象 <5> order_by(*field): 对查询结果排序 <6> reverse(): 对查询结果反向排序 <8> count(): 返回数据库中匹配查询(QuerySet)的对象数量。 <9> first(): 返回第一条记录 <10> last(): 返回最后一条记录 <11> exists(): 如果QuerySet包含数据,就返回True,否则返回False <12> values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列 <13> values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列 <14> distinct(): 从返回结果中剔除重复纪录
基于双下划线的模糊查询:
Book.objects.filter(price__in=[100,200,300]) # 价格在列表里面的记录 Book.objects.filter(price__gt=100) # 价格大于100 Book.objects.filter(price__lt=100) # 价格小于100 Book.objects.filter(price__range=[100,200]) # 区间 Book.objects.filter(title__contains="python") # 包含,大小写敏感 Book.objects.filter(title__icontains="python") # 包含,大小写不敏感 Book.objects.filter(title__startswith="py") # 以xx开头,大小写敏感 Book.objects.filter(pub_date__year=2012) # 2012年出版的
(5)删除记录
删除方法就是 delete(),它运行时立即删除对象,返回删除对象:
model_obj.delete()
例:
res = Book.objects.filter(id=1).delete() # 删除id=1 的记录 print(res) #结果: (1, {'app01.Book': 1})
也可以一次性删除多个对象。每个 QuerySet 都有一个 delete() 方法,它会一次性删除 QuerySet 中所有的对象。
如:删除2005年出版的所有书籍
Book.objects.filter(pub_date__year=2005).delete()
要注意的是: delete() 方法是 QuerySet 上的方法,但并不适用于 Manager 本身。这是一种保护机制,是为了避免意外地调用 Book.objects.delete() 方法导致所有的记录被误删除。如果你确认要删除所有的对象,那么你必须显式地调用:
Book.objects.all().delete()
另外,在 Django 删除对象时,会模仿 SQL 约束 ON DELETE CASCADE 的行为,即在删除一个对象时也会删除与它相关联的外键对象。如果不想级联删除,可以设置on_delete参数:
pubHouse = models.ForeignKey(to='Publisher', on_delete=models.SET_NULL, blank=True, null=True)
on_delete的models属性:
CASCADE:这就是默认的选项,级联删除,你无需显性指定它。 PROTECT: 保护模式,如果采用该选项,删除的时候,会抛出ProtectedError错误。 SET_NULL: 置空模式,删除的时候,外键字段被设置为空,前提就是blank=True, null=True,定义该字段的时候,允许为空。 SET_DEFAULT: 置默认值,删除的时候,外键字段设置为默认值,所以定义外键的时候注意加上一个默认值。 SET(): 自定义一个值,该值当然只能是对应的实体了 **set()官方案例** def get_sentinel_user(): return get_user_model().objects.get_or_create(username='deleted')[0] class MyModel(models.Model): user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET(get_sentinel_user), )
(6)修改记录
Book.objects.filter(title__startswith="py").update(price=120)
(7)代码:
from django.shortcuts import render, HttpResponse from app01.models import Book def index(request): # 1、创建记录-------------------------------------------------- # 方法1: # create方法的返回值book_obj就是插入book表中的葵花宝典这本书籍对象 book_obj = Book.objects.create(title="葵花宝典", state=True, price=100, publish="苹果出版社", pub_date="2012-12-12") # 方法2: book_obj = Book(title="python教程", state=True, price=100, publish="苹果出版社", pub_date="2012-12-12") book_obj.save() # 2、查询记录-------------------------------------------------------- print(Book.objects.all()) print(Book.objects.filter(title__contains="python")) # 3、删除记录-------------------------------------------------------- res = Book.objects.filter(id=1).delete() # 删除id=1 的记录 print(res) # (1, {'app01.Book': 1}) # 4、修改记录-------------------------------------------------------- Book.objects.filter(title__startswith="py").update(price=120) return HttpResponse(".....")
3、多表操作
(1)创建模型
实例:
作者模型(Author):一个作者有姓名和年龄。
作者详细模型(AuthorDetail):把作者的详情放到详情表,包含生日,手机号,家庭住址等信息。作者详情模型和作者模型之间是一对一的关系(one-to-one)
出版商模型(Publish
):出版商有名称,所在城市以及email。
书籍模型(Book): 书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many);
一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many)。
from django.db import models class Author(models.Model): """作者""" nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) age = models.IntegerField() # 与AuthorDetail建立一对一的关系 authorDetail = models.OneToOneField(to="AuthorDetail", on_delete=models.CASCADE) class AuthorDetail(models.Model): """作者详细""" nid = models.AutoField(primary_key=True) birthday = models.DateField() telephone = models.BigIntegerField() addr = models.CharField(max_length=64) class Publish(models.Model): """出版社""" nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) city = models.CharField(max_length=32) email = models.EmailField() class Book(models.Model): """书籍""" nid = models.AutoField(primary_key=True) title = models.CharField(max_length=32) publishDate = models.DateField() price = models.DecimalField(max_digits=5, decimal_places=2) # 与Publish建立一对多的关系,外键字段建立在多的一方 publish = models.ForeignKey(to="Publish", to_field="nid", on_delete=models.SET_NULL, null=True, blank=True) # 与Author表建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表 authors = models.ManyToManyField(to='Author', )
(2)添加表记录
一对多或一对一:
方式1: publish_obj=Publish.objects.get(nid=1) # 获取出版对象 book_obj=Book.objects.create(title="python",publishDate="2012-12-12",price=100,publish=publish_obj) # 将对象赋值给publish 方式2: book_obj=Book.objects.create(title="python",publishDate="2012-12-12",price=100,publish_id=1) # 将出版对象对应的id赋值给publish_id
多对多:
# 1、获取当前生成的书籍对象 book_obj=Book.objects.create(title="追风筝的人",price=200,publishDate="2012-11-12",publish_id=1) # 2、要为书籍绑定的作者对象 li=Author.objects.filter(name="li").first() # 在Author表中name为li的纪录 wang=Author.objects.filter(name="wang").first() # 在Author表中name为wang的纪录 # 3、绑定多对多关系,即向关系表book_authors中添加纪录 book_obj.authors.add(li, wang) # 将某些特定的 model 对象添加到被关联对象集合中。 等价于: book_obj.authors.add(*[])
多对多关系其它常用API:
book_obj.authors.remove() # 将某个特定的对象从被关联对象集合中去除。 等价于: book_obj.authors.remove(*[]) book_obj.authors.clear() #清空被关联对象集合 book_obj.authors.set() #先清空再设置
补充: class RelatedManager
"关联管理器"是在一对多或者多对多的关联上下文中使用的管理器。它存在于下面两种情况:
ForeignKey关系的“另一边”。像这样:
from django.db import models class Reporter(models.Model): # ... pass class Article(models.Model): reporter = models.ForeignKey(Reporter)
在上面的例子中,管理器reporter.article_set拥有下面的方法。
ManyToManyField关系的两边:
class Topping(models.Model): # ... pass class Pizza(models.Model): toppings = models.ManyToManyField(Topping)
这个例子中,topping.pizza_set 和pizza.toppings都拥有下面的方法。
add(obj1[, obj2, ...])
把指定的模型对象添加到关联对象集中。 例如: >>> b = Blog.objects.get(id=1) >>> e = Entry.objects.get(id=234) >>> b.entry_set.add(e) # Associates Entry e with Blog b. 在上面的例子中,对于ForeignKey关系,e.save()由关联管理器调用,执行更新操作。然而,在多对多关系中使用add()并不会调用任何 save()方法,而是由QuerySet.bulk_create()创建关系。 延伸: # 1 *[]的使用 >>> book_obj = Book.objects.get(id=1) >>> author_list = Author.objects.filter(id__gt=2) >>> book_obj.authors.add(*author_list) # 2 直接绑定主键 book_obj.authors.add(*[1,3]) # 将id=1和id=3的作者对象添加到这本书的作者集合中 # 应用: 添加或者编辑时,提交作者信息时可以用到.
create(**kwargs)
#创建一个新的对象,保存对象,并将它添加到关联对象集之中。返回新创建的对象: >>> b = Blog.objects.get(id=1) >>> e = b.entry_set.create( ... headline='Hello', ... body_text='Hi', ... pub_date=datetime.date(2005, 1, 1) ... ) # No need to call e.save() at this point -- it's already been saved. 这完全等价于(不过更加简洁于): >>> b = Blog.objects.get(id=1) >>> e = Entry( ... blog=b, ... headline='Hello', ... body_text='Hi', ... pub_date=datetime.date(2005, 1, 1) ... ) >>> e.save(force_insert=True) #要注意我们并不需要指定模型中用于定义关系的关键词参数。在上面的例子中,我们并没有传入blog参数给create()。Django会明白新的 Entry对象blog 应该添加到b中。
remove(obj1[, obj2, ...])
#从关联对象集中移除执行的模型对象: >>> b = Blog.objects.get(id=1) >>> e = Entry.objects.get(id=234) >>> b.entry_set.remove(e) # Disassociates Entry e from Blog b. #对于ForeignKey对象,这个方法仅在null=True时存在。
clear()
#从关联对象集中移除一切对象。 >>> b = Blog.objects.get(id=1) >>> b.entry_set.clear() #注意这样不会删除对象 —— 只会删除他们之间的关联。 #就像 remove() 方法一样,clear()只能在 null=True的ForeignKey上被调用。
set()方法
先清空,再设置,编辑书籍时即可用到
注意
对于所有类型的关联字段,add()、create()、remove()和clear(),set()都会马上更新数据库。换句话说,在关联的任何一端,都不需要再调用save()方法。
直接赋值:
通过赋值一个新的可迭代的对象,关联对象集可以被整体替换掉。
>>> new_list = [obj1, obj2, obj3]
>>> e.related_set = new_list
如果外键关系满足null=True,关联管理器会在添加new_list中的内容之前,首先调用clear()方法来解除关联集中一切已存在对象的关联。否则, new_list中的对象会在已存在的关联的基础上被添加。
(3)基于对象的跨表查询
- 一对多查询(Publish 与 Book)
正向查询(按字段:publish):
#查询主键为1的书籍的出版社所在的城市 book_obj = models.Book.objects.filter(nid=1).first() city = book_obj.publish.city print(city)
反向查询(按表名:book_set):
# 查询与苹果出版社关联的所有书籍对象集合 publish_obj = models.Publish.objects.get(name="苹果出版社") book_list = publish_obj.book_set.all() for book_obj in book_list: print(book_obj.title)
- 一对一查询(Author 与 AuthorDetail)
正向查询(按字段:authorDetail):
#查询张三的手机号 tel = models.Author.objects.get(name="张三").authorDetail.telephone print(tel)
反向查询(按表名:author):
# 查询所有住址在上海的作者的姓名 author_detail_list = models.AuthorDetail.objects.filter(addr="上海") for author_detail_obj in author_detail_list: print(author_detail_obj.author.name)
- 多对多查询 (Author 与 Book)
正向查询(按字段:authors):
# 查询python所有作者的名字以及手机号 authors = models.Book.objects.get(title="python").authors.all() for author_obj in authors: print(author_obj.name, author_obj.authorDetail.telephone)
反向查询(按表名:book_set):
#查询jack出过的所有书籍的名字 book_list = models.Author.objects.filter(name="jack").first().book_set.all() for book in book_list: print(book.title)
注意:
你可以通过在 ForeignKey() 和ManyToManyField()的定义中设置 related_name 的值来覆写 FOO_set 的名称。例如,如果 Article model 中做一下更改:
publish = ForeignKey(Book, related_name='bookList')
这样就可以直接通过 book_list = models.Author.objects.filter(name="jack").first().bookList.all() 来获取对象。
(4)基于双下划线的跨表查询
使用两个下划线来链接模型(model)间关联字段的名称,直到最终链接到你想要的 model 为止。
关键点:正向查询按字段,反向查询按表名。
- 一对多
# 查询苹果出版社出版过的所有书籍的名字与价格 # 正向查询 books1 = models.Book.objects.filter(publish__name="苹果出版社").values("title", "price") print(books1) # 反向查询 books2 = models.Publish.objects.filter(name="苹果出版社").values("book__title", "book__price") print(books2)
- 多对多
# 查询jack出过的所有书籍的名字 # 正向查询 books1 = models.Book.objects.filter(authors__name="jack").values("title") print(books1) # 反向查询 books2 = models.Author.objects.filter(name="jack").values("book__title") print(books2)
- 混合查询
# 查询橘子出版社出版过的所有书籍的名字以及作者的姓名 # 正向查询 info_list = models.Book.objects.filter(publish__name="橘子出版社").values("title", "authors__name") print(info_list) # 反向查询 info = models.Publish.objects.filter(name="橘子出版社").values("book__title", "book__authors__name") print(info) # 手机号以2开头的作者出版过的所有书籍名称以及出版社名称 # 正向查询 info1 = models.AuthorDetail.objects.filter(telephone__startswith=2).values("author__book__title", "author__book__publish__name") print(info1) # 反向查询 info2 = models.Book.objects.filter(authors__authorDetail__telephone__startswith=2).values("title", "publish__name") print(info2)
(5)聚合查询
aggregate(*args, **kwargs)
# 计算所有图书的平均价格 from django.db.models import Avg avg_price = models.Book.objects.all().aggregate(Avg('price')) print(avg_price) # {'price__avg': 113.333333}
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。键的名称是聚合值的标识符,值是计算出来的聚合值。键的名称是按照字段和聚合函数的名称自动生成出来的。如果你想要为聚合值指定一个名称,可以向聚合子句提供它:
from django.db.models import Avg avg_price = models.Book.objects.all().aggregate(avg_price=Avg('price')) print(avg_price) # {'avg_price': 113.333333}
向aggregate()子句中添加参数:
from django.db.models import Avg, Max, Min res = models.Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) print(res) # {'price__avg': 113.333333, 'price__max': Decimal('120.00'), 'price__min': Decimal('100.00')}
(6)分组查询 annotate(*args, **kwargs)
在models.py中添加如下模型:
class Employee(models.Model): """员工表""" nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32) age = models.IntegerField() salary = models.DecimalField(max_digits=8, decimal_places=2) dep = models.ForeignKey(to="Department", on_delete=models.SET_NULL, null=True, blank=True) class Department(models.Model): """部门表""" nid = models.AutoField(primary_key=True) name = models.CharField(max_length=32)
数据库迁移,并在表中添加数据:
单表查询:
# 查询每一个部门以及对应的员工数 """ SQL查询: select dep, Count(*) from employee group by dep; """ from django.db.models import Count info = models.Employee.objects.all().values("dep").annotate(Count("nid")) print(info) """ 结果: <QuerySet [ {'dep': 1, 'nid__count': 2}, {'dep': 2, 'nid__count': 3}, {'dep': 3, 'nid__count': 2} ]>
"""
多表查询:
# 查询每一个部门名称以及对应的员工数 """ SQL查询: select dep.name, Count(*) from employee left join department on employee.dep_id=department.nid group by department.name; """ from django.db.models import Count info = models.Department.objects.all().annotate(c=Count("employee")).values("name", "c") print(info) """ 结果: <QuerySet [ {'name': '财务部', 'c': 2}, {'name': '开发部', 'c': 3}, {'name': '人力资源部', 'c': 2} ]> """
# 统计每一本书的作者个数 res = models.Book.objects.all().annotate(count=Count("authors")).values("title", "count") print(res) # 统计每一个出版社的最便宜的书 res = models.Publish.objects.all().annotate(min_price=Min("book__price")) for obj in res: print(obj.name, obj.min_price) # 统计每一本以Py开头的书籍的作者个数 res = models.Book.objects.filter(title__startswith="Py").annotate(count=Count("authors")).values("title", "count") print(res) # 统计不止一个作者的图书 res = models.Book.objects.all().annotate(author_num=Count("authors")).\ filter(author_num__gt=1).values("title", "author_num") print(res) # 根据一本图书作者数量的多少对查询集QuerySet进行排序 res = models.Book.objects.all().annotate(author_num=Count("authors")).\ order_by("author_num").values("title", "author_num") print(res) # 查询各个作者出的书的总价格 res = models.Author.objects.all().annotate(sum=Sum("book__price")).values("name", "sum") print(res)
(7)F查询
- 比较同一个 model 实例中两个不同字段的值
# 查询评论数大于收藏数的书籍 from django.db.models import F Book.objects.filter(commnetNum__gt=F('keepNum'))
- F() 对象之间以及 F() 对象和常数之间的加减乘除和取模的操作
# 查询评论数大于收藏数2倍的书籍 Book.objects.filter(commnetNum__gt=F('keepNum')*2)
#将每一本书的价格提高30元: Book.objects.all().update(price=F("price")+30)
(8)Q查询
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR 语句),你可以使用Q 对象。
Q 对象可以使用 &(and) 和 |(或) 操作符组合起来。当一个操作符在两个Q 对象上使用时,它产生一个新的Q 对象。
from django.db.models import Q
# 查询作者为jack或者wang的书籍 bookList=Book.objects.filter(Q(authors__name="jack")|Q(authors__name="wang"))
Q 对象可以使用 ~ 操作符取反,这允许组合正常的查询和取反(NOT) 查询:
# 查询作者叫wang并且出版时间不在2017年的书籍 bookList=Book.objects.filter(Q(authors__name="wang") & ~Q(publishDate__year=2017)).values_list("title")
查询函数可以混合使用Q 对象和关键字参数。所有提供给查询函数的参数(关键字参数或Q 对象)都将"AND”在一起。但是,如果出现Q 对象,它必须位于所有关键字参数的前面。
# 2016年或2017年出版的名字包含python的书籍 bookList=Book.objects.filter(Q(publishDate__year=2016) | Q(publishDate__year=2017),title__icontains="python")
from django.shortcuts import render, HttpResponse from django.db.models import Count, Min, Sum from app01 import models def index2(request): # 1、创建记录-------------------------------------------------- detail_obj1 = models.AuthorDetail.objects.create(birthday="1963-12-10", telephone=128938383838, addr="上海") detail_obj2 = models.AuthorDetail.objects.create(birthday="1976-3-23", telephone=338383838, addr="南京") detail_obj3 = models.AuthorDetail.objects.create(birthday="1977-12-3", telephone=2348383838, addr="广州") publish_obj1 = models.Publish.objects.create(name="苹果出版社", city="天津", email="cddd@163.com") publish_obj2 = models.Publish.objects.create(name="橘子出版社", city="深圳", email="wewe@163.com") publish_obj3 = models.Publish.objects.create(name="北京出版社", city="北京", email="sff@163.com") author_obj1 = models.Author.objects.create(name="mark", age=43, authorDetail_id=2) # 通过id添加外键,authorDetail_id表示id author_obj2 = models.Author.objects.create(name="张三", age=42, authorDetail=detail_obj3) # 通过对象添加外键,authorDetail表示一个作者详情对象 author_obj3 = models.Author.objects.create(name="jack", age=56, authorDetail_id=1) book_obj1 = models.Book.objects.create(title="Python", publishDate="2012-12-12", price=100, publish=publish_obj1) book_obj2 = models.Book.objects.create(title="java", publishDate="2014-12-12", price=120, publish_id=2) book_obj3 = models.Book.objects.create(title="数据库", publishDate="2014-3-12", price=120, publish_id=3) # 多对多新增记录: book_obj1.authors.add(author_obj1, author_obj2) book_obj2.authors.add(author_obj2, author_obj3) book_obj3.authors.add(author_obj3) # 2、基于对象的跨表查询-------------------------------------------------------- # 一对多 # 正向查询:按字段 查询主键为1的书籍的出版社所在的城市 book_obj = models.Book.objects.filter(nid=1).first() city = book_obj.publish.city print(city) # 反向查询 :按表名:book_set 查询与苹果出版社关联的所有书籍对象集合 publish_obj = models.Publish.objects.get(name="苹果出版社") book_list = publish_obj.book_set.all() for book_obj in book_list: print(book_obj.title) # 一对一 # 正向查询:按字段 查询张三的手机号 tel = models.Author.objects.get(name="张三").authorDetail.telephone print(tel) # 反向查询(按表名:author):查询所有住址在上海的作者的姓名 author_detail_list = models.AuthorDetail.objects.filter(addr="上海") for author_detail_obj in author_detail_list: print(author_detail_obj.author.name) # 多对多 # 正向查询(按字段:authors):查询python所有作者的名字以及手机号 authors = models.Book.objects.get(title="python").authors.all() for author_obj in authors: print(author_obj.name, author_obj.authorDetail.telephone) # 反向查询(按表名:book_set):查询jack出过的所有书籍的名字 book_list = models.Author.objects.filter(name="jack").first().book_set.all() for book in book_list: print(book.title) # 3、基于双下划线的跨表查询-------------------------------------------------------- # 一对多 # 查询苹果出版社出版过的所有书籍的名字与价格 # 正向查询 books1 = models.Book.objects.filter(publish__name="苹果出版社").values("title", "price") print(books1) # 反向查询 books2 = models.Publish.objects.filter(name="苹果出版社").values("book__title", "book__price") print(books2) # 多对多 # 查询jack出过的所有书籍的名字 # 正向查询 books1 = models.Book.objects.filter(authors__name="jack").values("title") print(books1) # 反向查询 books2 = models.Author.objects.filter(name="jack").values("book__title") print(books2) # 混合使用 # 查询橘子出版社出版过的所有书籍的名字以及作者的姓名 # 正向查询 info_list = models.Book.objects.filter(publish__name="橘子出版社").values("title", "authors__name") print(info_list) # 反向查询 info = models.Publish.objects.filter(name="橘子出版社").values("book__title", "book__authors__name") print(info) # 手机号以2开头的作者出版过的所有书籍名称以及出版社名称 # 正向查询 info1 = models.AuthorDetail.objects.filter(telephone__startswith=2).values("author__book__title", "author__book__publish__name") print(info1) # 反向查询 info2 = models.Book.objects.filter(authors__authorDetail__telephone__startswith=2).values("title", "publish__name") print(info2) # 4、聚合查询-------------------------------------------------------- # 计算所有图书的平均价格 from django.db.models import Avg avg_price = models.Book.objects.all().aggregate(avg_price=Avg('price')) print(avg_price) # {'avg_price': 113.333333} from django.db.models import Avg, Max, Min res = models.Book.objects.aggregate(Avg('price'), Max('price'), Min('price')) print(res) # {'price__avg': 113.333333, 'price__max': Decimal('120.00'), 'price__min': Decimal('100.00')} # 5、分组查询---------------------------------------------------------- # 查询每一个部门以及对应的员工数 """ SQL查询: select dep, Count(*) from employee group by dep; """ from django.db.models import Count info = models.Employee.objects.all().values("dep").annotate(Count("nid")) print(info) """ 结果: <QuerySet [ {'dep': 1, 'nid__count': 2}, {'dep': 2, 'nid__count': 3}, {'dep': 3, 'nid__count': 2} ]> """ # 查询每一个部门名称以及对应的员工数 """ SQL查询: select dep.name, Count(*) from employee left join department on employee.dep_id=department.nid group by department.name; """ from django.db.models import Count info = models.Department.objects.all().annotate(c=Count("employee")).values("name", "c") print(info) """ 结果: <QuerySet [ {'name': '财务部', 'c': 2}, {'name': '开发部', 'c': 3}, {'name': '人力资源部', 'c': 2} ]> """ # 统计每一本书的作者个数 res = models.Book.objects.all().annotate(count=Count("authors")).values("title", "count") print(res) # 统计每一个出版社的最便宜的书 res = models.Publish.objects.all().annotate(min_price=Min("book__price")) for obj in res: print(obj.name, obj.min_price) # 统计每一本以Py开头的书籍的作者个数 res = models.Book.objects.filter(title__startswith="Py").annotate(count=Count("authors")).values("title", "count") print(res) # 统计不止一个作者的图书 res = models.Book.objects.all().annotate(author_num=Count("authors")).\ filter(author_num__gt=1).values("title", "author_num") print(res) # 根据一本图书作者数量的多少对查询集QuerySet进行排序 res = models.Book.objects.all().annotate(author_num=Count("authors")).\ order_by("author_num").values("title", "author_num") print(res) # 查询各个作者出的书的总价格 res = models.Author.objects.all().annotate(sum=Sum("book__price")).values("name", "sum") print(res) return HttpResponse("ok")
六、Django组件——cookie与session
1、会话跟踪技术
在JavaWeb中,客户向某一服务器发出第一个请求开始,会话就开始了,直到客户关闭了浏览器,会话结束。 在一个会话的多个请求中共享数据,这就是会话跟踪技术。
会话跟踪技术使用Cookie或session完成。
我们知道HTTP协议是无状态协议,也就是说每个请求都是独立的!无法记录前一次请求的状态。但HTTP协议中可以使用Cookie来完成会话跟踪!在Web开发中,使用session来完成会话跟踪,session底层依赖Cookie技术。
2、cookie
(1)什么是cookie?
Cookie翻译成中文是小甜点,小饼干的意思。在HTTP中它表示服务器送给客户端浏览器的小甜点。其实Cookie是key-value结构,类似于一个python中的字典。
当浏览器第一次访问服务器,由服务器创建Cookie,然后通过响应发送给客户端。客户端会保存Cookie,并会标注出Cookie的来源(哪个服务器的Cookie)。当客户端再次向服务器发出请求时会把所有这个服务器Cookie包含在请求中发送给服务器,这样服务器就可以识别客户端了!
(2)cookie规范
- Cookie大小上限为4KB;
- 一个服务器最多在客户端浏览器上保存20个Cookie;
- 一个浏览器最多保存300个Cookie;
上面的数据只是HTTP的Cookie规范,但在浏览器大战的今天,一些浏览器为了打败对手,为了展现自己的能力起见,可能对Cookie规范“扩展”了一些,例如每个Cookie的大小为8KB,最多可保存500个Cookie等!但也不会出现把你硬盘占满的可能! - 不同浏览器之间是不共享Cookie的。也就是说在你使用IE访问服务器时,服务器会把Cookie发给IE,然后由IE保存起来,当你在使用FireFox访问服务器时,不可能把IE保存的Cookie发送给服务器。
(3)Cookie与HTTP头
Cookie是通过HTTP请求头和响应头在客户端和服务器端传递的:
- Cookie:请求头,客户端发送给服务器端;
- 格式:Cookie: a=A; b=B; c=C。即多个Cookie用分号分隔,
- Set-Cookie:响应头,服务器端发送给客户端;
- 一个Cookie对象一个Set-Cookie: Set-Cookie: a=A Set-Cookie: b=B Set-Cookie: c=C
- Cookie的覆盖
如果服务器端发送重复的Cookie那么会覆盖原有的Cookie,例如客户端的第一个请求服务器端发送的Cookie是:Set-Cookie: a=A;第二请求服务器端发送的是:Set-Cookie: a=AA,那么客户端只留下一个Cookie,即:a=AA。
(4)django中的cookie语法
- 设置cookie:
rep = HttpResponse(...) 或 rep = render(request, ...) 或 rep = redirect() rep.set_cookie(key,value,...) rep.set_signed_cookie(key,value,salt='加密盐',...)
源码:
class HttpResponseBase: def set_cookie(self, key, 键 value='', 值 max_age=None, 超长时间 cookie需要延续的时间(以秒为单位) 如果参数是\ None`` ,这个cookie会延续到浏览器关闭为止。 expires=None, 超长时间 expires默认None ,cookie失效的实际日期/时间。 path='/', Cookie生效的路径, 浏览器只会把cookie回传给带有该路径的页面,这样可以避免将 cookie传给站点中的其他的应用。 / 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问 domain=None, Cookie生效的域名 你可用这个参数来构造一个跨站cookie。 如, domain=".example.com" 所构造的cookie对下面这些站点都是可读的: www.example.com 、 www2.example.com 和an.other.sub.domain.example.com 。 如果该参数设置为 None ,cookie只能由设置它的站点读取。 secure=False, 如果设置为 True ,浏览器将通过HTTPS来回传cookie。 httponly=False 只能http协议传输,无法被JavaScript获取 (不是绝对,底层抓包可以获取到也可以被覆盖) ): pass
- 获取cookie:
request.COOKIES
- 删除cookie:
response.delete_cookie("cookie_key",path="/",domain=name)
(5)案例1:显示上次访问时间。
(6)案例2:显示上次浏览过的商品。
3、session
(1)session介绍
Session是服务器端技术,利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的session对象,由于 session为用户浏览器独享,所以用户在访问服务器的web资源时 ,可以把各自的数据放在各自的session中,当用户再去访问该服务器中的其它web资源时,其它web资源再从用户各自的session中 取出数据为用户服务。
(2)django中session语法
#1、设置Sessions值 request.session['session_name'] ="admin" #2、获取Sessions值 session_name = request.session["session_name"] #3、删除Sessions值 del request.session["session_name"] #4、删除当前的会话数据并删除会话的Cookie。这用于确保前面的会话数据不可以再次被用户的浏览器访问 flush() #5、获取session值 get(key, default=None) fav_color = request.session.get('fav_color', 'red') #6、删除 pop(key) fav_color = request.session.pop('fav_color') #7、查看所有key keys() #8、查询所有键值对 items() #9、setdefault() #10、用户session的随机字符串 request.session.session_key # 11、将所有Session失效日期小于当前日期的数据删除 request.session.clear_expired() #12、 检查用户session的随机字符串在数据库中是否存在 request.session.exists("session_key") # 13、删除当前用户的所有Session数据 request.session.delete("session_key") #14、设置超时时间 request.session.set_expiry(value) * 如果value是个整数,session会在xx秒后失效。 * 如果value是个datatime或timedelta,session就会在这个时间后失效。 * 如果value是0,用户关闭浏览器session就会失效。 * 如果value是None,session会依赖全局session失效策略。
(3)session配置
Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。 a. 配置 settings.py SESSION_ENGINE = 'django.contrib.sessions.backends.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_SAVE_EVERY_REQUEST = False # 是否每次请求都保存Session,默认修改之后才保存(默认)
(4)登录案例
#login.html:登录页面,提供登录表单; #index1.html:主页,显示当前用户名称,如果没有登录,显示您还没登录; #index2.html:主页,显示当前用户名称,如果没有登录,显示您还没登录;
思考:如果第二个人再次再同一个浏览器上登录,django-session表会怎样?
七、Django组件——forms
1、forms组件的校验字段功能
如:用户注册
模型models.py:
class UserInfo(models.Model): name = models.CharField(max_length=32) pwd = models.CharField(max_length=32) email = models.EmailField() tel = models.CharField(max_length=32)
模板 register.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="" method="post"> {% csrf_token %} <div> <label for="user">用户名</label> <p><input type="text" name="name" id="name"></p> </div> <div> <label for="pwd">密码</label> <p><input type="password" name="pwd" id="pwd"></p> </div> <div> <label for="r_pwd">确认密码</label> <p><input type="password" name="r_pwd" id="r_pwd"></p> </div> <div> <label for="email">邮箱</label> <p><input type="text" name="email" id="email"></p> </div> <input type="submit"> </form> </body> </html>
视图views.py:
from django import forms from django.forms import widgets from django.shortcuts import render, HttpResponse from app01 import models wid_01 = widgets.TextInput(attrs={"class": "form-control"}) wid_02 = widgets.PasswordInput(attrs={"class": "form-control"}) class UserForm(forms.Form): name = forms.CharField(max_length=32, widget=wid_01) pwd = forms.CharField(max_length=32, widget=wid_02) r_pwd = forms.CharField(max_length=32, widget=wid_02) email = forms.EmailField(widget=wid_01) tel = forms.CharField(max_length=32, widget=wid_01) def register(request): if request.method == "POST": form = UserForm(request.POST) if form.is_valid(): # 字段合法 print(1, form.cleaned_data) # 所有干净的字段以及对应的值 return HttpResponse("OK") else: print(2, form.cleaned_data) # 合法字段 print(3, form.errors, type(form.errors)) # ErrorDict : {"校验错误的字段":["错误信息",]} print(4, form.errors.get("email")) # 当输入错误格式的email时就会捕获到这个错误信息:ErrorList ["错误信息",] return HttpResponse(str(form.errors)) form = UserForm() return render(request, "register.html", locals())
2、forms组件的渲染标签功能
views.py:
方式1:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>注册页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form action="" method="post"> {% csrf_token %} <div> <label for="">用户名</label> {{ form.name }} </div> <div> <label for="">密码</label> {{ form.pwd }} </div> <div> <label for="">确认密码</label> {{ form.r_pwd }} </div> <div> <label for=""> 邮箱</label> {{ form.email }} </div> <div> <label for=""> 电话</label> {{ form.tel }} </div> <input type="submit" class="btn btn-default pull-right"> </form> </div> </div> </div> </body> </html>
效果:
方式2:
<form action="" method="post"> {% csrf_token %} {% for field in form %} <div> <label for="">{{ field.label }}</label> {{ field }} </div> {% endfor %} <input type="submit" class="btn btn-default pull-right"> </form>
效果:
如果想要显示中文,就需要到UserForm类中设置label属性:
方式3:
<form action="" method="post"> {% csrf_token %} {{ form.as_p }} <input type="submit" class="btn btn-default pull-right"> </form>
3、在页面显示错误提示信息
from django import forms from django.forms import widgets from django.shortcuts import render, HttpResponse from app01 import models wid_01 = widgets.TextInput(attrs={"class": "form-control"}) wid_02 = widgets.PasswordInput(attrs={"class": "form-control"}) class UserForm(forms.Form): name = forms.CharField(max_length=32, widget=wid_01, label="用户名") pwd = forms.CharField(max_length=32, widget=wid_02) r_pwd = forms.CharField(max_length=32, widget=wid_02) email = forms.EmailField(widget=wid_01) tel = forms.CharField(max_length=32, widget=wid_01) def register(request): if request.method == "POST": form = UserForm(request.POST) if form.is_valid(): # 字段合法 print(1, form.cleaned_data) # 所有干净的字段以及对应的值 return HttpResponse("OK") else: print(2, form.cleaned_data) # 合法字段 print(3, form.errors, type(form.errors)) # ErrorDict : {"校验错误的字段":["错误信息",]} print(4, form.errors.get("email")) # 当输入错误格式的email时就会捕获到这个错误信息:ErrorList ["错误信息",] return render(request, "register.html", locals()) form = UserForm() # 实例化 return render(request, "register.html", locals())
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>注册页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form action="" method="post"> {% csrf_token %} {% for field in form %} <div> <label for="">{{ field.label }}</label> {{ field }} <span class="pull-right" style="color: red">{{ field.errors.0 }}</span> </div> {% endfor %} <input type="submit" class="btn btn-default"> </form> </div> </div> </div> </body> </html>
4、局部钩子与全局钩子
from django import forms from django.forms import widgets from django.core.exceptions import ValidationError from django.shortcuts import render, HttpResponse from app01 import models wid_01 = widgets.TextInput(attrs={"class": "form-control"}) wid_02 = widgets.PasswordInput(attrs={"class": "form-control"}) class UserForm(forms.Form): name = forms.CharField(max_length=32, widget=wid_01, label="用户名") pwd = forms.CharField(max_length=32, widget=wid_02, label="密码") r_pwd = forms.CharField(max_length=32, widget=wid_02, label="确认密码") email = forms.EmailField(widget=wid_01, label="邮箱") tel = forms.CharField(max_length=32, widget=wid_01, label="电话") # 局部钩子 def clean_name(self): # 函数名规则:clean_字段名 """规定用户名不能为纯数字""" val = self.cleaned_data.get("name") if not val.isdigit(): return val else: raise ValidationError("用户名不能是纯数字!") # 全局钩子 def clean(self): """校验两次密码必须一致""" pwd = self.cleaned_data.get("pwd") r_pwd = self.cleaned_data.get("r_pwd") if pwd == r_pwd: return self.cleaned_data else: raise ValidationError('两次密码不一致!') def register(request): if request.method == "POST": form = UserForm(request.POST) if form.is_valid(): # 字段合法 print(1, form.cleaned_data) # 所有干净的字段以及对应的值 return HttpResponse("OK") else: clean_error = form.errors.get("__all__") print(clean_error) return render(request, "register.html", locals()) form = UserForm() # 实例化 return render(request, "register.html", locals())
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <h3>注册页面</h3> <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <form action="" method="post" novalidate> {% csrf_token %} {% for field in form %} <div> <label for="">{{ field.label }}</label> {{ field }} <span class="pull-right" style="color: red"> {% if field.label == '确认密码' %} <span>{{ clean_error.0 }}</span> {% endif %} {{ field.errors.0 }} </span> </div> {% endfor %} <input type="submit" class="btn btn-default"> </form> </div> </div> </div> </body> </html>
八、Django组件——用户认证
1、auth模块
from django.contrib import auth
(1)authenticate()
提供了用户认证,即验证用户名以及密码是否正确,一般需要username和password两个关键字参数。
如果认证信息有效,会返回一个 User 对象。当我们试图登陆一个从数据库中直接取出来不经过authenticate()的User对象就会报错。
user=authenticate(username='amy',password='123456')
(2)login(HttpRequest, user)
该函数接受一个HttpRequest对象,以及一个认证了的User对象,
此函数使用django的session框架给某个已认证的用户附加上session id等信息。
from django.contrib.auth import authenticate, login def login(request): username = request.POST.get('username') password = request.POST.get('password') user = authenticate(username=username, password=password) if user is not None: login(request, user) # Redirect to a success page. ... else: # Return an 'invalid login' error message. ...
(3)logout(request) 注销用户
当调用该函数时,当前请求的session信息会全部清除。该用户即使没有登录,使用该函数也不会报错。
from django.contrib.auth import logout def logout_view(request): logout(request) # Redirect to a success page.
2、user对象
user 对象属性:username, password(必填项),password是用哈希算法保存到数据库的。
(1) is_authenticated()
用于检查用户是否已经通过了认证。返回值为 True或False 。
通过认证并不意味着用户拥有任何权限,甚至也不检查该用户是否处于激活状态,这只是表明用户成功的通过了认证。
在后台用request.user.is_authenticated()判断用户是否已经登录,如果True则可以向前台展示request.user.name
要求:
1 用户登陆后才能访问某些页面,
2 如果用户没有登录就访问该页面的话直接跳到登录页面
3 用户在跳转的登陆界面中完成登陆后,自动访问跳转到之前访问的地址
方法1:
def my_view(request): if not request.user.is_authenticated(): # 如果没有登录就跳转到登录页面 return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path))
方法2:
使用装饰器login_requierd()
from django.contrib.auth.decorators import login_required @login_required def my_view(request): ...
若用户没有登录,则会跳转到django默认的登录URL '/accounts/login/ ' (这个值可以在settings文件中通过LOGIN_URL进行修改)。并传递当前访问url的绝对路径 (登陆成功后,会重定向到该路径)。
(2)create_user() 创建用户
from django.contrib.auth.models import User user = User.objects.create_user(username='xxx',password='xxx',email='xxx')
(3)check_password(password) 校验密码
如,当用户需要修改密码的时候,首先要让他输入原来的密码 ,如果给定的字符串通过了密码检查,返回 True
(4)set_password() 修改密码
user = User.objects.get(username='amy') user.set_password(password='111') user.save
(5)示例
注册:
from django.contrib.auth.models import User from django.shortcuts import render, HttpResponse, redirect from app01 import models def sign_up(request): state = None if request.method == 'POST': username = request.POST.get('username', '') password = request.POST.get('password', '') repeat_password = request.POST.get('repeat_password', '') email = request.POST.get('email', '') if not password == repeat_password: state = "两次密码不一致" else: # 检查用户名是否已经存在 if User.objects.filter(username=username): state = '用户名已经存在!' else: new_user = User.objects.create_user(username=username, password=password, email=email) new_user.save() return redirect('/index/') content = {'state': state, 'user': None} return render(request, 'sign_up.html', {"content": content})
sign_up.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="post"> {% csrf_token %} 用户名:<input type="text" name="username"/> 密码:<input type="password" name="password"/> 确认密码:<input type="password" name="repeat_password"/> 邮箱:<input type="email" name="email"/> <input type="submit" value="注册"/> {% if content.state %} <span style="color: red">{{ content.state }}</span> {% endif %} </form> </body> </html>
修改密码:
from django.contrib.auth.models import User from django.contrib.auth.decorators import login_required from django.contrib.auth import authenticate, login, logout from django.shortcuts import render, HttpResponse, redirect from app01 import models @login_required def set_password(request): """需改密码""" user = request.user state = None if request.method == 'POST': old_password = request.POST.get('old_password', '') new_password = request.POST.get('new_password', '') repeat_password = request.POST.get('repeat_password', '') if user.check_password(old_password): if not new_password: state = '不能为空' elif new_password != repeat_password: state = '两次密码不一致' else: user.set_password(new_password) user.save() return redirect("/accounts/login/") else: state = '密码错误' content = { 'user': user, 'state': state, } return render(request, 'set_password.html', {"content": content}) def login_view(request): """登录""" next_url = request.GET.get("next") if request.method == "POST": username = request.POST.get("username") password = request.POST.get("password") user = authenticate(username=username, password=password) if user is not None: login(request, user) if next_url: return redirect(next_url) else: return HttpResponse("ok") return render(request, "login.html")
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="post"> {% csrf_token %} <p>用户名:<input type="text" name="username"/></p> <p>密码:<input type="password" name="password"/></p> <p><input type="submit" value="登录"/></p> </form> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form method="post"> {% csrf_token %} <p>旧密码:<input type="password" name="old_password"/></p> <p>新密码:<input type="password" name="new_password"/></p> <p>确认密码:<input type="password" name="repeat_password"/></p> <p><input type="submit" value="提交"/></p> </form> </body> </html>
九、Django组件——中间件
1、中间件概念
中间件,是介于request与response处理之间的一道处理过程,相对比较轻量级,在全局上改变django的输入与输出,即可以对request和response作批量操作。
当请求进来时会穿过一层层的中间件到达视图,视图作相应处理后返回响应数据,响应数据再通过层层中间件返回给用户:
2、自定义中间件
(1)中间件中有五种方法
process_request()
process_view()
process_response()
process_exception()
(2)自定义中间件,即编写一个类,继承MiddlewareMixin
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse class Md1(MiddlewareMixin): def process_request(self, request): print("Md1请求") def process_response(self, request, response): print("Md1返回") return response class Md2(MiddlewareMixin): def process_request(self, request): print("Md2请求") # return HttpResponse("Md2中断") # 加上这句,请求不会到达视图函数里面 def process_response(self, request, response): print("Md2返回") return response
settings.py:
from django.shortcuts import render, HttpResponse def index(request): print("in view...") return HttpResponse("OK")
结果:
修改middleware如下:
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse class Md1(MiddlewareMixin): def process_request(self, request): print("Md1请求") # return HttpResponse("Md1中断") def process_response(self, request, response): print("Md1返回") return response def process_view(self, request, callback, callback_args, callback_kwargs): print("Md1 view") class Md2(MiddlewareMixin): def process_request(self, request): print("Md2请求") # return HttpResponse("Md2中断") def process_response(self, request, response): print("Md2返回") return response def process_view(self, request, callback, callback_args, callback_kwargs): print("Md2 view")
结果:
结果:
流程:
在process_view中可以调用视图函数:
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse class Md1(MiddlewareMixin): def process_request(self, request): print("Md1请求") # return HttpResponse("Md1中断") def process_response(self, request, response): print("Md1返回") return response def process_view(self, request, callback, callback_args, callback_kwargs): # return HttpResponse("hello") response = callback(request, *callback_args, **callback_kwargs) return response class Md2(MiddlewareMixin): def process_request(self, request): print("Md2请求") # return HttpResponse("Md2中断") def process_response(self, request, response): print("Md2返回") return response def process_view(self, request, callback, callback_args, callback_kwargs): print("Md2 view")
结果:
当process_view有返回值时,会越过其他的process_view以及视图函数,但是所有的process_response都还会执行。
修改middleware:
from django.utils.deprecation import MiddlewareMixin from django.shortcuts import HttpResponse class Md1(MiddlewareMixin): def process_request(self, request): print("Md1请求") def process_response(self, request, response): print("Md1返回") return response def process_view(self, request, callback, callback_args, callback_kwargs): print("md1 process_view...") def process_exception(self, request, exception): print("md1 process_exception...") return HttpResponse("error") class Md2(MiddlewareMixin): def process_request(self, request): print("Md2请求") def process_response(self, request, response): print("Md2返回") return response def process_view(self, request, callback, callback_args, callback_kwargs): print("md2 process_view...") def process_exception(self, request, exception): print("md2 process_exception...") return HttpResponse("error")
结果:
当视图函数出现异常时,process_exception就会被触发:
3、自带的中间件:
'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
十、Django组件——分页器
1、数据量不大的情况
import random from django.shortcuts import render, HttpResponse from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from app01.models import * def index(request): # 批量导入数据: # book_list = [] # for i in range(100): # book_list.append(Book(title="book" + str(i), price=30.00 + i * 2, publishDate="2012-10-23", # publish_id=random.randint(1, 3))) # Book.objects.bulk_create(book_list) # 批量创建记录 book_list = Book.objects.all() paginator = Paginator(book_list, 10) # 每页10条 print("count:", paginator.count) # 数据总数 print("num_pages", paginator.num_pages) # 总页数 print("page_range", paginator.page_range) # 页码的列表 page1 = paginator.page(1) # 第1页的page对象 for i in page1: # 遍历第1页的所有数据对象 print(i) print(page1.object_list) # 第1页的所有数据 page2 = paginator.page(2) print(page2.has_next()) # 是否有下一页 print(page2.next_page_number()) # 下一页的页码 print(page2.has_previous()) # 是否有上一页 print(page2.previous_page_number()) # 上一页的页码 paginator = Paginator(book_list, 10) page = request.GET.get('page', 1) current_page = int(page) try: book_list = paginator.page(page) except PageNotAnInteger: book_list = paginator.page(1) except EmptyPage: book_list = paginator.page(paginator.num_pages) return render(request, "index.html", {"book_list": book_list, "paginator": paginator, "currentPage": current_page})
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <div class="container"> <h4>分页器</h4> <ul> {% for book in book_list %} <li>{{ book.title }} -----{{ book.price }}</li> {% endfor %} </ul> <ul class="pagination" id="pager"> {% if book_list.has_previous %} <li class="previous"><a href="/app02/index/?page={{ book_list.previous_page_number }}">上一页</a></li> {% else %} <li class="previous disabled"><a href="#">上一页</a></li> {% endif %} {% for num in paginator.page_range %} {% if num == currentPage %} <li class="item active"><a href="/app02/index/?page={{ num }}">{{ num }}</a></li> {% else %} <li class="item"><a href="/app02/index/?page={{ num }}">{{ num }}</a></li> {% endif %} {% endfor %} {% if book_list.has_next %} <li class="next"><a href="/app02/index/?page={{ book_list.next_page_number }}">下一页</a></li> {% else %} <li class="next disabled"><a href="#">下一页</a></li> {% endif %} </ul> </div> </body> </html>
2、数据量大的情况
def index(request): book_list = Book.objects.all() paginator = Paginator(book_list, 5) page = request.GET.get('page', 1) currentPage = int(page) # 如果页数特别多时,限制显示的index if paginator.num_pages > 11: # 只显示10页的index if currentPage - 5 < 1: # 当前页码小于6 pageRange = range(1, 11) elif currentPage + 5 > paginator.num_pages: # 当前页码+5大于了总页码 pageRange = range(currentPage - 5, paginator.num_pages + 1) else: pageRange = range(currentPage - 5, currentPage + 5) else: pageRange = paginator.page_range try: print("当前页码:", page) book_list = paginator.page(page) except PageNotAnInteger: book_list = paginator.page(1) except EmptyPage: book_list = paginator.page(paginator.num_pages) return render(request, "index.html", locals())
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> <div class="container"> <h4>分页器</h4> <ul> {% for book in book_list %} <li>{{ book.title }} -----{{ book.price }}</li> {% endfor %} </ul> <ul class="pagination" id="pager"> {% if book_list.has_previous %} <li class="previous"><a href="/app02/index/?page={{ book_list.previous_page_number }}">上一页</a></li> {% else %} <li class="previous disabled"><a href="#">上一页</a></li> {% endif %} {% for num in pageRange %} {% if num == currentPage %} <li class="item active"><a href="/app02/index/?page={{ num }}">{{ num }}</a></li> {% else %} <li class="item"><a href="/app02/index/?page={{ num }}">{{ num }}</a></li> {% endif %} {% endfor %} {% if book_list.has_next %} <li class="next"><a href="/app02/index/?page={{ book_list.next_page_number }}">下一页</a></li> {% else %} <li class="next disabled"><a href="#">下一页</a></li> {% endif %} </ul> </div> </body> </html>
可以将分页的代码写到一个文件中,要使用分页时直接调用既可以。
十一、Django与Ajax
1、Ajax介绍
AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML,现在更多使用json数据)。
- 同步交互:客户端发出一个请求后,需要等待服务器响应结束后,才能发出第二个请求;
- 异步交互:客户端发出一个请求后,无需等待服务器响应结束,就可以发出第二个请求。
Ajax特点:
- 使用Javascript技术向服务器发送异步请求;
- 浏览器页面局部刷新。
2、基于jQuery的Ajax实现
<button class="send_Ajax">send_Ajax</button> <script> $(".send_Ajax").click(function(){ $.ajax({ url:"/handle_Ajax/", # URL type:"POST", # 请求方式 data:{username:"xxx",password:123}, # 请求数据 # 回调函数:成功执行,发生错误等情况发生时做些什么操作 success:function(data){ console.log(data) }, error: function (jqXHR, textStatus, err) { console.log(arguments); }, complete: function (jqXHR, textStatus) { console.log(textStatus); }, statusCode: { '403': function (jqXHR, textStatus, err) { console.log(arguments); }, '400': function (jqXHR, textStatus, err) { console.log(arguments); } } }) }) </script>
3、应用场景
(1) 用户名是否已被注册
在注册表单中,当用户填写了用户名后,把光标移开后,会自动向服务器发送异步请求。
服务器返回true或false,返回true表示这个用户名已经被注册过,返回false表示没有注册过。
客户端得到服务器返回的结果后,确定是否在用户名文本框后显示“用户名已被注册”的错误信息!
(2) 基于Ajax进行登录验证
用户在表单输入用户名与密码,通过Ajax提交给服务器,服务器验证后返回响应信息,客户端通过响应信息确定是否登录成功,成功,则跳转到首页,否则,在页面上显示相应的错误信息。
4、请求头ContentType
ContentType指的是请求体的编码类型,常见的类型共有3种:
- application/x-www-form-urlencoded
最常见的 POST 提交数据的方式,浏览器的原生表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。
请求体:
POST http://www.example.com HTTP/1.1 Content-Type: application/x-www-form-urlencoded;charset=utf-8 ... user=amy&age=22
- multipart/form-data
使用表单上传文件时,必须让表单的 enctype 等于 multipart/form-data
POST http://www.example.com HTTP/1.1 Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA ------WebKitFormBoundaryrGKCBY7qhFd3TrwA Content-Disposition: form-data; name="user" yuan ------WebKitFormBoundaryrGKCBY7qhFd3TrwA Content-Disposition: form-data; name="file"; filename="chrome.png" Content-Type: image/png PNG ... content of chrome.png ... ------WebKitFormBoundaryrGKCBY7qhFd3TrwA--
首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。然后 Content-Type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容。消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以 --boundary 开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以 --boundary-- 标示结束。
- application/json
表示消息主体是序列化后的 JSON 字符串。
5、文件上传
(1)基于form表单的文件上传
def avatar(request): # print("body:", request.body) # 原始的请求体数据 print("get:", request.GET) # GET请求数据 print("post:", request.POST) # POST请求数据 print("files:", request.FILES) # 上传的文件数据 return render(request, "avatar.html")
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} 用户名 <input type="text" name="user"> 头像 <input type="file" name="avatar"> <input type="submit"> </form> </body> </html>
结果:
get: <QueryDict: {}> post: <QueryDict: {'csrfmiddlewaretoken': ['vVPsFNXCJpl3P0aUIaS1ABYkczxtZmrIa1RJEsoqGIQ9zrJ6CITbm7LkulaLwp71'], 'user': ['qw']}> files: <MultiValueDict: {'avatar': [<InMemoryUploadedFile: 照片.jpg (image/jpeg)>]}>
(2)基于Ajax的文件上传
def avatar(request): if request.is_ajax(): print(request.GET) # GET请求数据 print(request.POST) # POST请求数据 print(request.FILES) # 上传的文件数据 return HttpResponse("ok") return render(request, "avatar.html")
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form> {% csrf_token %} 用户名 <input type="text" id="user"> 头像 <input type="file" id="avatar"> <input type="button" id="ajax-submit" value="ajax-submit"> </form> <script src="http://libs.baidu.com/jquery/1.9.0/jquery.js"></script> <script> $("#ajax-submit").click(function () { var formdata = new FormData(); formdata.append("user", $("#user").val()); formdata.append("csrfmiddlewaretoken", $("[name='csrfmiddlewaretoken']").val()); formdata.append("avatar_img", $("#avatar")[0].files[0]); $.ajax({ url: "", //默认当前URL type: "post", data: formdata, processData: false, // 不处理数据 contentType: false, // 不设置内容类型 success: function (data) { console.log(data); } }) }) </script> </body> </html>