Django 框架学习基础笔记

Django 千锋培训读书笔记

  • Django
    • Django简介
    • BS架构
    • web项目搭建1 -- 虚拟环境
    • web项目搭建2 - Django项目
      • Django项目1 - 安装django
      • Django项目2 - 创建项目
      • Django项目3 - 启动服务器
      • Django项目4 - 新建APP
      • Django项目5 - 视图开发
        • 1.定义视图-View
        • 2.为视图设置url-conf
        • 3.发送请求,访问项目
      • Django项目6 - 模板
        • 1.定义模板配置
        • 2.定义模板文件
        • 3.视图中使用模板
        • 4.View向模板中传递数据
        • 5.模板中使用View传来的数据
      • 静态资源
        • 静态资源:
        • 静态资源访问
          • 1. 配置静态资源访问前缀
          • 2.静态资源根路径
          • 3.静态资源管理
          • 4.扩展
      • 虚拟环境补充
  • View
    • 基本开发流程
    • 配置url-pattern
      • 一般配置
      • 正则配置
      • 技巧
    • 请求处理
      • 如何发送请求
        • 请求手段
        • 请求方式
        • 如何发送请求
      • 携带请求参数
        • 超链接传参
        • 表单传参
      • 接收请求参数
        • 接收Get请求参数
        • 接收Post请求参数
        • 补充:接收多值参数 = getlist()
      • Get和Post请求区别
      • 补充:命名路径
        • 注意:如果定义了命名路径,则View中必须接收参数
      • HttpRequest对象(了解)
      • 请求方式限定(了解)
    • HttpResponse
      • 基本响应
      • 跳转
      • 跳转实例
      • 跳转流程示意
    • 作业:
  • Model
    • ORM
    • Model开发过程
    • Model-Field
    • Field-Options
    • Model-Meta
    • Model-API 之 增删改
    • Model-API 之 查询
      • Manager + QuerySet
      • 基本查询方法
        • filter() - QuerySet (**重点**)
      • QuerySet限制操作
      • 条件查询
      • 映射查询
      • 聚合-aggregate
      • 分组-annotate
      • F对象
        • **当查询条件中需要另外的列时,可以使用F**
      • Q对象
        • **当需要 “或 (|) 非(~)” 逻辑时,可以使用Q**
      • 原生sql的支持
  • Model
    • 关联关系
    • 关联关系搭建
      • 查询
      • 增加
      • 删除
      • 修改
    • QuerySet 高级特性:懒加载 Lazy-load
    • 事务
    • Cookie
      • 使用过程-写cookie
      • 使用过程-读cookie
    • Session
      • Session使用
      • Session生命周期
      • 写Session
      • 读Session
        • **注意session没有中文问题,没有长度限制,相比存在客户端的cookie更安全**
      • 清除Session
      • Session存储位置
      • Session实现原理(session和cookie的联系)
    • Cookie Session选择
    • 全局错误视图设置
    • 事务控制
    • 基于View的事务控制(了解)
      • **坑:如下情况,事务依然提交**
    • 作业
  • Template
    • DTL
    • 传值
      • 变量-取值
      • 分支判断:if 标签
        • 语法格式
        • 运算
      • 遍历:for 标签
      • 过滤器(非重点)
    • 模板继承
    • 反向解析
      • 访问路径硬编码
      • 使用反向解析
      • 反向解析管理
      • 补充:命名路径的反向解析
    • 验证码
      • 开发过程-1
      • 开发过程-2
      • 开发过程-3
      • 开发过程-4
    • 文件上传
      • 开发过程-1
      • 开发过程-2
      • 开发过程-3
        • 3.1 定义Model
        • 3.2 通过Model,存储上传数据
      • 开发过程-4
      • 补充
    • 分页
      • Page对象
      • View-1
      • View-2
      • View-3
    • 中间件 - Middleware(了解)
    • CSRF(了解)
    • Django内置后台管理-定制(了解)
      • 定制1
      • 定制3
      • 定制4
      • 定制5
      • 定制6
      • 定制7
      • 定制8
      • 定制9
      • 定制10
    • Form(了解)

Django

Django简介

django是目前流行很广泛的python-web框架,提供一站式的web开发支持,重量级框架,为python-web项目的开发提供了极大的便利。

BS架构

B/S结构(Browser/Server,浏览器/服务器模式),web项目主流构建结构

Django 框架学习基础笔记_第1张图片

web项目搭建1 – 虚拟环境

由于python的第三方类库很多样化,而且不同的python项目所需要的第三方依赖库不尽相同,所以如果想在单台主机上运行不同的项目,需要开辟虚拟环境

在当前主机上的,一个独立于本地环境的一个python运行环境

python3后自带了虚拟环境工具pyvenv。python3的类库中增加了一个venv模块

  • python -m venv env9 #创建虚拟环境,在当前目录下 (注意虚拟环境的目录中不能有中文)
  • 激活虚拟环境,才可使用虚拟环境

    在虚拟环境目录下的Scrips下执行 : activate

  • 安装依赖的不同版本

    pip install "pillow > 4.3"

    pip install "pillow==5"

    pip install "Django==2.0.2"

    pip install django

  • 关闭虚拟环境

    在虚拟环境下执行deactivate即可

  • linux中指令(了解)

    激活:source evn9/bin/activate
    关闭:.evn99/bin/deactive

web项目搭建2 - Django项目

Django项目1 - 安装django

虚拟环境中,安装django:pip install "django==2.0.2"

Django项目2 - 创建项目

Django 框架学习基础笔记_第2张图片
在这里插入图片描述
在这里插入图片描述
Django 框架学习基础笔记_第3张图片

Django项目3 - 启动服务器

django内置了一款服务器,用于开发过程的web项目测试使用

  • python manage.py runserver #即可开启服务器
  • http://localhost:8000 #是服务器的默认访问地址(默认的ip和端口)

设置服务器的ip和port

  • 在settings.py中修改配置:ALLOWED_HOSTS = ['*']
  • python manage.py runserver 192.168.0.3:8989 开启服务器,并设置服务器的ip和端口
  • http://192.168.0.3:8989 即可访问服务器,并且此时服务器可以在整个网络中提供服务!!

Django项目4 - 新建APP

project的开发过程中,会有模块化开发,如电商系统中的用户模块,订单模块,OA系统中的财务模块,人力模块等。每个模块都是project的一个APP,APP内是相关模块的功能集合,包含所有相关的功能及完整的实现。将一个project划分为多个APP是一个解耦的过程,整个项目结构松散,利于维护。

在虚拟环境下执行项目目录下的manage.py文件: python manage.py startapp first_app 即可在project中构建一个APP:在当前目录下生成一个目录,目录名:“first_app”

APP的目录结构如下

Django 框架学习基础笔记_第4张图片

注意:每个App都必须安装在project中

settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'first_app', #安装自己的app
]

Django项目5 - 视图开发

1.定义视图-View

在app的views.py中定义视图,每个视图都是一个函数

1.接收http请求,给出响应

2.调度Model,组织数据 (忽略)

3.转发Template(V),渲染数据,得到一个承载着model查询出的数据的网页(html)(忽略)

#View函数,可以接收、处理客户端请求
def hello_world(request):
    print("hello world")
    return HttpResponse("hey this is 服务器") #响应请求

2.为视图设置url-conf

在urls.py文件中为视图指定访问路径

from django.contrib import admin
from django.urls import path
from first_app.views import hello_world #导入自己的view
urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/',hello_world) #为自己的view,设置访问路径
]
##则,http://localhost:8000/hello/可以访问到自己的view

3.发送请求,访问项目

View而接受访问,并会给以响应

http://localhost:8000/hello/
http://192.168.0.3:8989/hello/

Django项目6 - 模板

template:动态生成html,进而可以更好的响应client(浏览器)

模板文件就是一个html格式的文件

1.定义模板配置

在settings.py文件中告知django,模板文件的所在目录

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR, 'templates')], #模板目录:project根目录下的templates目录
        'APP_DIRS': True,#每个app下的templates目录也是模板目录
        ....
    }

2.定义模板文件

文件位置:在app下创建templates目录,或使用project下的templates目录均可

模板文件中的所有内容,将会成为响应内容,传输到客户端

ht1.html

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
    <p>hello <span style="color:red">worldspan>p>
body>
html>

3.视图中使用模板

通过模板解析获得响应内容

from django.shortcuts import render,HttpResponse,loader
from first_app.models import User

def xxx(request):
	temp = loader.get_template("ht1.html") #构建模板对象,指向ht1.html
	content = temp.render(request=request) #模板解析,返回响应内容
	return HttpResponse(content) #响应
快捷方式
return render(request, "ht1.html"#响应

注意:此时的页面,只是静态页面;要做动态页面需要数据的介入

4.View向模板中传递数据

数据格式:Dict

示例数据:{“name”:“臧红久”}

#1.从数据库中获取数据
conn = MySQLdb.connect(...)
cursor = conn.cursor(DictCursor)
cursor.execute("select id,name from t_user where id=1")
user = cursor.fetchone()
#2.获取模板
temp = loader.get_template("ht1.html") #构建模板对象,指向ht1.html
#3.将数据传入模板
content2 = temp.render({"name": user.get("name")},request)
#4.响应
return HttpResponse(content2) 
#响应并传递数据 的快捷方式
return render(request, "ht1.html", {"name": "臧红久"})

5.模板中使用View传来的数据


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
	
    {{ name }}
body>
html>

<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Titletitle>
head>
<body>
	
     <p>hello <span style="color:red"> {{ name }} span>p>
body>
html>

如此,则模板中除了静态内容外,还有了动态内容 {{name}}

静态资源

静态资源:

1> 项目中的 css文件 ,js文件,image文件,属于静态资源

2> 所谓静态,是指这些文件的内容是不变的

3> 和View函数不同,静态资源没有自己的访问路径配置,那么该如何访问静态资源?

静态资源访问

1. 配置静态资源访问前缀
settings.py中
STATIC_URL = '/static/'

则访问路径以“/static/”为前缀,则会被认为是要访问静态资源。(否则请求只会去访问View函数)

<link rel="stylesheet" href="/static/aaa.css"> #访问aaa.css文件

<img src="/static/bbb.jpg" width="200px"/> #访问bbb.jpg文件

<script src="/static/ccc.js">script> #访问ccc.js文件
2.静态资源根路径

那么问题是:django到哪里为请求去寻找静态资源?

"已安装"的APP中的static目录是默认寻找根路径

<link rel="stylesheet" href="/static/aaa.css"> #到静态资源根路径下查找 aaa.css文件

<img src="/static/bbb.jpg" width="200px"/> #到静态资源根路径下查找 bbb.jpg文件

<script src="/static/c133/ccc.js">script> #到静态资源根路径下查找 c133目录,从c133目录下找 ccc.js

注意:/static/并不参与静态资源查找,只是表名该请求是要找静态资源

/static/bbb.jpg : /static/ ==要找静态资源

bbb.jpg ==要找的静态资源是 bbb.jpg

然后找到静态资源根路径,去查找bbb.jpg

3.静态资源管理

如果多个APP下有同名静态文件,会如何?

解决方案:每个APP的静态资源都有自己的独立目录即可

: 在每个APP的static目录下都建立一个命名独立的目录

4.扩展

新增自己的静态文件根目录

除默认的静态文件根目录外,增加几个静态文件的根目录

settings.py
STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static9'),
                     "E:\\view"]

当然,在自己的新增的目录中也要为APP开辟独立的目录,防止APP之间静态资源命名名冲突

补充:静态资源访问路径 软编码

问题:

​ 如果settings.py中 STATIC_URL = '/static/'发生改变,将有较大的维护成本

解决方案:{%load static %} 加载static标签

自动补全前边的 STATIC_URL

虚拟环境补充

第三方包:virtualenv 和 virtualenvwrapper-win

pip install virtualenv #安装virtualenv包,功能和 venv一致
pip install virtualenvwrapper-win #管理虚拟环境的包(linux下是 : pip install virtualenvwrapper)
#安装之后,配置环境变量
WORKON_HOME=E:\zhj\venv99 #此目录用来统一存放所有创建的虚拟环境,默认目录是:c:\users\用户名=用户目录
#创建虚拟环境  
mkvirtualenv  env9  #即可在WORKON_HOME目录下创建名为env9的虚拟环境,且在创建后,自动激活
rmvirtualenv evn9 #删除虚拟环境
#激活虚拟环境
workon evn9 #如此即可
#退出虚拟环境
deactivate
#查看虚拟环境列表
workon

View

Django中提供了View组件用来充当MVC中的C-controller

主要职责:

  • 接收请求
  • 调度Model
  • 转发Template(显示数据相关逻辑)

基本开发流程

1. 定义在App下的views.py中,每个View都是一个函数,函数中需要一个参数,用来接收请求对象

def hello(request):#request=请求对象
    print("abc")

2. 为每个View函数定义响应

注意:每个View必须有响应,即必须返回一个HttpResponse

from django.shortcuts import render,HttpResponse
def hello(request):
    print("abc")
    return HttpResponse("hilo~~~") #接收客户端请求后,给以响应

3. 为每个View函数定义访问路径

urls.py
from django.contrib import admin
from django.urls import path
from view_test_app import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/',views.hello) #添加自己的view配置
]

4. 启动服务器

django内置了一个供开发使用的 web-服务器,虽然不适合生产环境,但用户开发测试,特别便利

python mange.py runserver #默认ip和端口 localhost:8000

python manage.py runserver 192.168.0.3:8989 #修改了默认的ip和port

注意:修改了ip和port后,需要到settings.py中增加一个设置:ALLOWED_HOSTS = ["*"]

5. 访问View

http://localhost:8000/hello/
http://192.168.0.3:8989/hello/

配置url-pattern

一般配置

from django.contrib import admin
from django.urls import path
from view_test_app import views
urlpatterns = [
    path('admin/', admin.site.urls),
    path('hello/',views.xxx) # http://localhost:8000/hello/
	path('hello/world/',views.xxx) # http://localhost:8000/hello/world/
	path('hello/world/a/b/',views.xxx) # http://localhost:8000/hello/world/a/b/
]

正则配置

re_path

re_path(r'hello/\d{3}/',views.hello2)  #http://127.0.0.1:8000/hello/125/
re_path(r'hello/\d{2,5}/\w+/',views.hello2) #http://127.0.0.1:8000/hello/45343/ab1_2/
                                          #http://127.0.0.1:8000/zzzzhello/45343/ab1_2/zzzz/zzzz
re_path(r'hello/[a-d]*/$',views.hello2) #http://127.0.0.1:8000/hello/abccdda/
                                        #http://127.0.0.1:8000/zzzzhello/abccdda/
#建议写法:通过^和$明确限定起始和结尾
re_path(r'^hello/[a-d]*/$',views.hello2) #http://127.0.0.1:8000/hello/abccdda/

**注意:在使用正则时,^ 和 $可以保证精确匹配 **

技巧

一个Project会有很多App,每个App会有很多View函数,如果都在一个urls.py中配置,则项目的管理成本将急剧上升

每个App不仅逻辑独立,也希望在url-pattern的配置上也相互隔离

便于管理 维护(各司其职,职责单一)

1.在App目录下,新建一个urls.py,在其中配置App内的每个view

from django.urls import path,re_path
from view_test_app import views
urlpatterns=[
    re_path(r'^hello1/(?P\d{3})/$', views.hello),
    re_path(r'^hello2/(?P\d{2,5})/(?P\w+)/$', views.hello2),
    path('hello3/', views.hello3),
]
#如上三个View的访问路径前要加上全局urls.py中的路径“test1/”:
#   http://localhost:8000/test1/hello1/123
#   http://localhost:8000/test1/hello2/123/zhj
#   http://localhost:8000/test1/hello3/

2.在全局的urls.py中使用 include 包含各个app的urls文件

from django.urls import path,include
urlpatterns = [
 	...
    path('test1/',include("view_test_app.urls"))#导入外部的urls模块
    #如上的“test1”将成为 “view_test_app.urls”每一个path路径的前缀
]

3.如此:每个App都在全局的urls.py中有一个自己的前缀;在自己的urls.py中定义自己的path;

实现了对于访问路径的高效管理

1> 诸多view的配置,分发到每个app中各自管理,秩序不乱

2> 每个app在全局的urls中登记,设置访问前缀,利于维护访问路径名

3> 每个app的访问前缀,可以描述具体的逻辑,使得路径有明确的逻辑区分

4.进一步管理:如果在一个App的众多path中,又有关系逻辑关联紧密的可以进一步管理

from django.urls import path,re_path,include
from . import views
urlpatterns=[
    path(...),
    ....,
    path('user/vip/',include([
						path('a/',views.hello), #http://.../test1/user/vip/a/
						path('b/',views.hello2), #http://.../test1/user/vip/b/
						path('c/d/',views.hello3), #http://.../test1/user/vip/c/d/
                     ])
    ),
    path(...),
    ...
]

5.至此:View的url配置则有了管理方案:

1> 每个App定义自己的urls.py,其中定义自己的url-conf

2> Project的全局urls.py中引入各个App的url-conf,并定义访问前缀,区分、隔离各个App

3> App内部的url-conf可以根据逻辑进一步区分、隔离

请求处理

如何发送请求

请求手段

  • 超链接
  • 表单
#http://192.168.0.3:9000/test1/user/vip/a/
<a href="/test1/user/vip/a/">go~</a>
<form action="/test1/user/vip/a/" method="get/post">
	<input type="text"/>
    ...
    <input type="submit" value="提交"/>
</form>

请求方式

http请求最常用的两种方式:GET / POST

  • 超链接发送的是get方式的http请求
  • 表单可以发送get方式的请求,也可以post方式的http请求 (默认发送get方式的请求)

如何发送请求

实用场景:1> 需要用户输入信息使用Form,否则可以选择超链接。

​ 2>form中如果不涉及隐私数据,且用户输入项较少,选择get请求方式;

​ 反之选择post请求方式。

携带请求参数

客户端(浏览器)访问服务器时,需要向服务器传递数据(请求参数),以让服务器可以区别处理

比如:

​ 1.登录时,请求中要携带用户名和密码两个参数,服务器才知道哪个用户要登录

​ 2.删除时,请求中要携带一个id参数,服务器才知道要删除哪个数据

超链接传参

url?key=value&key=value…

key是参数名,value是参数值

其中【?】是请求路径和请求参数的分隔

其中【&】是多个请求参数之间的分隔

<a href="/test1/user/vip/a/?age=18">xxx</a>
<a href="/test1/user/vip/a/?id=1&name=zhj">xxx</a>

表单传参

表单中的控件,name属性值为参数名,value属性值为参数值

#param: age=xx 和 name=xx  和  gender 和 language
<form action="/test1/user/vip/a/">
    <input type="text" name="age"/>
    <input type="text" name="name"/>
    
    <input type="radio" name="gender" value="male"/><br/>
    <input type="radio" name="gender" value="female"/><br/>
    
    <select name="language">
        <option value="en">英文option>
        <option value="zh">中文option>
        <option value="fr">法文option>
    select>
    <input type="submit" value="提交"/>
form>

接收请求参数

接收Get请求参数

1.超链接:http://localhost:8000/test1/user/vip/a/?age=18&name=zhj

2.method="get"的form:
    <form action="http://localhost:8000/test1/user/vip/a/" method="get">
        <input type="text" name="age"/>
        <input type="text" name="name"/>
        <input type="radio" name="gender" value="male"/><input type="radio" name="gender" value="female"/><select name="language">
            <option value="en">英文option>
            <option value="zh">中文option>
            <option value="fr">法文option>
        select>
        <input type="submit" value="提交"/>
    form>

#接收请求参数 request.GET['参数名'] 或 request.GET.get("参数名") 均可
def test(request):
    request.GET['age'] # 18(不建议,容易抛出异常) 
    request.GET.get('age') # 18  注意参数类型!
    request.GET.get("name") #zhj 注意参数类型!
    request.GET.get("gender") # 选中的radio中的value
    request.GET.get("language") #选中的option的value

接收Post请求参数

<form action="http://localhost:8000/test1/user/vip/a/" method="post">
    {% csrf_token %}
    <input type="text" name="age"/>
    <input type="text" name="name"/>
    <input type="radio" name="gender" value="male"/><input type="radio" name="gender" value="female"/><select name="language">
        <option value="en">英文option>
        <option value="zh">中文option>
        <option value="fr">法文option>
    select>
    <input type="submit" value="提交"/>
form>

#接收请求参数   request.POST['参数名']   request.POST.get("参数名")
def test(request):
    request.POST['age'] # 18 
    request.POST.get('age') # 18
    request.POST.get("name") #zhj
    request.POST.get("gender") # 选中的radio中的value
    request.POST.get("language") #选中的option的value

注意

request.GET[‘age’] 如果没有值则报错

request.GET.get(‘age’) 如果没有值返回None

POST同样!

补充:接收多值参数 = getlist()

#age有两个值
http://localhost:8000/test1/user/vip/a/?age=18&age=19name=zhj
#hobby会有多个值
<form action="http://localhost:8000/test1/user/vip/a/" method="post">
    <input type="checkbox" name="hobby" value="football"/>足球
    <input type="checkbox" name="hobby" value="basketball"/>篮球
    <input type="checkbox" name="hobby" value="volleyball"/>排球
    <input type="submit" value="提交"/>
form>

如上,请求参数中同一个key上有多个值,View中如何接收?如下代码是错误的!

def test(request):
    age = request.GET.get("age") #能否接收多个值,只能收最后一个值
    hobby = request.POST.get("hobby")#能否接收多个值,只能收最后一个值

应该用getlist,才可以接收一个参数上的多个值!

def test(request):
    age = request.GET.getlist("age") #接收多个值 返回list = ["18","19"]
    hobby = request.POST.getlist("hobby")#接收多个值 list = ["footbal","basketball"] 

Get和Post请求区别

  • POST安全性好于GET,GET中的请求参数直接暴露于url上;传递密码等敏感数据时,不使用GET。
  • GET对数据长度有限制,URL 的最大长度是 2048 个字符,POST无限制。
  • GET只允许 ASCII 字符。POST可以传递各种数据,二进制也可以。
  • GET可以缓存,POST不能缓存
  • HTTP的语义中GET请求用来查询数据,POST用来提交数据

补充:命名路径

# :名称为“age”的路径,此种路径可以匹配任何内容
# http://localhost:8000/hello/18/    # http://localhost:8000/hello/19/
path('hello//',views.abc) 
# 同样
# http://localhost:8000/hello/18/zhj/
path('hello///',views.def) 
def abc(request,age):#age 接收路径捕获的值
	....
def def(request,age,name):#age和name分别接收   捕获的值
	...
re_path(r'^hello1/(?P\d{3})/$', hello1) #定义名为age的正则命名路径
re_path(r'^hello2/(?P\d{2,5})/(?P\w+)/$', hello2)#定义名为age和name的正则命名路径
def hello(req,age):# age 接收捕获组的值
    print("hello world",a)
    return HttpResponse("hey~~")
def hello2(req,age,name):# age 接收捕获组的值  name 接收捕获组的值
    print("hello world",a,b)
    return HttpResponse("hey~~")

注意:如果定义了命名路径,则View中必须接收参数

转换器

非正则命名路径中可以使用转换器,约束每个路径捕获数据的类型

  • int:接收整数
  • str:字符串(了解)
  • slug:接收[数字,字母,下划线,中划线]四种字符(了解)

如果路径上的值不符合转换器类型,则访问不到对应view函数 (404页面)

path('hello////',hello)
#http://ip:port/hello/12/abc/asdf123_sdf-/
def hello(req,age,name,nick):#注意age此时不再是str,而是int
    print("hello world",age,name,nick)
    return HttpResponse("hey~~")

HttpRequest对象(了解)

View中参数request,类型为HttpRequest

  • GET = QueryDict {“key”:[xx,xx],“key2”:[xx,xx,xx]

    url?id=1&name=zhj&hobby=ft&hobby=bt

    GET={“id”:[“1”],“name”:[“zhj”],“hobby”:[“ft”,“bt”]}

    GET.get(“id”) 返回列表中的最后一个值

    GET.getlist(“id”) 返回整个列表

  • POST = QueryDict

  • method = 请求方式

  • path = 请求路径

请求方式限定(了解)

@require_http_methods(["GET"]) #此View,只接收Get请求
def test1(request):
    ....
    
@require_http_methods(["GET","POST"]) #此View,接收Get请求和POST请求
def test1(request):
    ....

HttpResponse

基本响应

响应对象,负责给浏览器客户端响应内容

from django.shortcuts import HttpResponse
return HttpResonse("hello world")#响应内容为:“hello world”

如果要显示更复杂的内容,或者说,实际的开发过程,响应是需要使用模板的

即把要响应的内容定义在模板中,然后将模板处理后的内容响应给浏览器客户端

temp = loader.get_template("test.html")#加载模板文件
content = temp.render();#模板渲染,返回渲染后的内容
return HttpResponse(content)#将内容响应给client

建议使用更简易的:

return render(request, "test.html")

以上是响应的最基本使用,接下来要系统分析,响应的处理

跳转

响应要更好的处理,势必会用到跳转,如图:

Django 框架学习基础笔记_第5张图片

不同组件间的功能衔接,即,跳转

需要跳转的情景有:

  1. 一个View接到请求后,请求需要的逻辑处理完后,最后都需要给出响应。

    而,响应内容的生成一般不由View负责,Template是专业的响应内容生成者,则此时需要

    View和Template之间做衔接、跳转

  2. 不同的功能由不同的View负责,则当功能之间需要衔接时,就会涉及

    View和View间做衔接、跳转

跳转方式有

  • 转发

    转发发生在一个请求内的跳转,用于View到Template间的跳转,

    render(request, "test.html") 即为在一个请求内由View转发跳转到Template

  • 重定向

    重定向发生在不同请求之间,用于View之间的跳转

    redirect("另外的View的请求路径:/a/b/c/")

跳转实例

def hello(req):
    print("hello world")
    return redirect("/test/hello2/") # 重定向到View
    #return HttpResponseRedirect("/test/hello2/")

def hello2(req):
    print("hello world2")
    return render(req,"test.html") # 转发到Template

跳转流程示意

Django 框架学习基础笔记_第6张图片

重定向和转发区别:

  1. 转发用于View和Template,重定向用于View和View
  2. 转发是一次请求内的跳转(地址栏不变),重定向会自动触发第二次请求(地址栏会改变)
  3. 重定向可以访问其他域的位置

作业:

0.技术点练习,熟练
1.ems-regist
2.ems-login

Model

搭建ORM,简化python和数据库的通信

mysqlclient、pymysql… conn cursor 繁琐

ORM

当和数据库通信时,直接使用python操作数据库,通信编码过程繁琐。

ORM是解决此问题的主要流行方案。

ORM – Object Relational Mapping 对象关系映射

  • python对象 关系表 搭建映射
  • 对象属性 表中的列 搭建映射
  • 使得对象和关系表可以直接通信,极大简化通信过程

class User:

​ def __init__(self,id,name,age):

​ self.id=id

​ self.name=name

​ self.age=age

t_user

id name age

1 zs 18 ==> User(1,“zs”,18)

2 ls 19 ==> User(2,“ls” ,19)

User.all()===[User,user,user,user]

Django的Model章节,也不例外的践行了ORM,为model层的开发提供可极大便利

Django中的ORM的搭建,核心是model类,所以Model章节的内容,将围绕model类展开

Model开发过程

  • 安装数据库驱动:pip install mysqlclient-1.3.13-cp35-cp35m-win_amd64.whl

  • 设置数据库连接参数(settings.py)

    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql', #连接类型
            'NAME': 'db9', #数据库名称
            'USER':'root', #用户名
            'PASSWORD':'222222', #密码
            'HOST':'localhost', #ip
            'PORT':'3306' # 端口
        }
    }
    #注意 NAME USER HOST 等,必须大写
    
  • 安装app;设置开发模式;设置时区(细节补充配置)

    INSTALLED_APPS = [
        'django.contrib.admin',
        .....
        'my_app' #安装自己的app
    ]
    
    DEBUG = True #开发模式,可以看到更多日志
    
    #时区设置
    LANGUAGE_CODE = 'zh-Hans'
    TIME_ZONE = 'Asia/Shanghai'
    USE_TZ = False
    
  • 定义model类

    class User(models.Model):
        name = models.CharField(max_length=20)  # varchar(20)
        age = models.IntegerField()   # int
        birth = models.DateTimeField()  # datetime
    
  • 生成移植文件

    会扫描已安装app中的Model类

    会在app目录下的migrations目录中生成一些文件,用户数据库移植

    python manage.py makemigration

    Django 框架学习基础笔记_第7张图片

  • 执行移植文件

    通过移植文件中的信息,在数据库中生成数据表

    python manage.py migrate 可以生成的表结构如下:

    CREATE TABLE "my_app_user" (
    	"id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, 
    	"name" varchar(20) NOT NULL, 
    	"age" integer NOT NULL, 
    	"birth" datetime NOT NULL
    );
    
  • 测试Model操作

    1> 在terminal中:python manage.py shell  #进入测试环境
    2> 导入自己的Model类:from my_app.models import User
    3> User.objects.all() #查询所有用户信息
    4> User.objects.get(pk=1).delete() #删除id为1的用户
    

如上,在操作数据时,完全没有接触数据库先关的任何内容,只是通过操作python的Object,就对数据库完成了对应操作,则至此,ORM搭建完毕,通信过程变得直接,简便

补充:

1> 如果修改了model,则可以再次“生成移植文件”然后“执行移植文件”重新同步数据库即可

Model-Field

  • AutoField 一般不用直接使用,Model中默认的主键类型

    Django默认给每个Model一个主键字段(如果没有自己定义的话)
    id = models.AutoField(primary_key=True)
    
  • IntegerField 4字节 int

  • BigIntegerField 8字节 bigint

  • SmallIntegerField 2字节 smallint

  • FloatField 底层采用float类型 double

  • DecimalField(max_digits=5,decimal_places=2) 底层采用Decimal类型 decimal

    必填:max_digits=5 #共有几位数

    必填:decimal_places=2#其中小数位占几位

  • CharField(max_length=20) varchar(20)

    max_length是必填属性

  • TextField longtext

  • BooleanField 接收“True/False” tinyint

  • NullBooleanField 接收“True/False/None” tinyint

  • DateTimeField datetime

  • DateField date

    可选:auto_now=true 可做修改时间记录

    可选:auto_now_add=True 可做首次添加时间

    注意:以上两属性之一指定后,该字段不允许被编辑

  • TimeField time

  • FileField varchar

  • ImageField varchar

  • ForeignKey(to=关系对方的类或类名或‘self’,on_delete=级联选项)

  • OneToOneFiled(to=关系对方的类或类名或‘self’,on_delete=级联选项)

  • ManyToManyFiled(to=关系对方的类或类名或‘self’)

Field-Options

  • null 默认False,不能为空

    name = models.CharField(max_length=20,null=True) #可为空
    
  • default 定义默认值

    name = models.CharField(max_length=20,default="zhj9")
    age = models.IntegerField(default=18)
    birth = models.DateTimeField(default="2018-12-12")
    

    默认值不会作用在数据库上,而是django自己的约定,通过Model.save()时,会使用默认值

  • primary_key

    id = models.AutoFiled(primary_key=True) #默认追加,不用自己定义
    id = models.CharField(primary_key=True,max_length=32)#如果需要字符串类型的ID可以自己定义
    
  • unique 列是否唯一

    name = models.CharField(max_length=20,unique=True)
    
  • db_column 自定义列名,默认和field同名

    name = models.CharField(max_length=20,db_column="name9")
    
  • db_index 是否在列上建立索引

    name = models.CharField(max_length=20,db_index=True)
    

Model-Meta

  • db_table 设置表名

  • unique_together 设置联合唯一约束

    class MyModel(models.Model):
       salary = models.DecimalField(...)
       .....
       age = models.xxx
       name = models.xxx
       class Meta:
            #unique_together = (("salary", "salary2"),("age","age2","age3"))
            unique_together = ("salary", "salary2")
            db_table="my_user"
    

补充:ORM搭建方式

1> 建立Model,然后通过移植创建表;

2> 建立Model,手动创建表;

​ 在Model上声明要映射的表名;

​ 在Model的每个Field中声明要映射的列名。

Model-API 之 增删改

增加
user = User(name="zhj",age=18,birth=datetime.now())
user2 = User(name="zhj",age=18,birth='2018-12-12 12:12:40')
user.save()
user2.save()
或者
user = User.objects.create(name="zzz",age=19,birth=datetime.now()) #创建对象并保存数据,一步完成
##注意,save()没有返回值,create()返回保存的对象
删除
User.objects.get(pk=3).delete()
User.objects.all().delete()
更新
user = User.objects.get(pk=3)
user.name="new_name"
user.save()

Model-API 之 查询

Manager + QuerySet

  • Model继承关系:User(Model(ModelBase))
  • ModelBase为User指派一个属性:objects=Manager()
    • objects,存储一个Manager对象,用来支持查询功能。
  • Manager对象中查询功能的相关方法是由QuerySet提供实现

基本查询方法

如下所有查询方法都是QuerySet实现的,只不过每个Model中都会有一个Manager,Manager接收到如下方法的调用时,会去调用QuerySet的方法,最终实现数据查询。

所以QuerySet这个类 是查询动作的核心支撑

Manager的任务是管理QuerySet,即,在需要的时候去调用QuerySet的对应方法

  • all() - QuerySet

    User.objects.all() #返回QuerySet,其中是所有User的数据
    #, ,...]>
    
  • get() - 对象

    #id=1
    User.objects.get(pk=1) #返回查到的数据,没有数据或数据多于1条 报错
    #name='zhj' 而且 age=18的数据
    User.objects.get(name="zhj",age=18)#返回查到的数据,没有数据或数据多于1条 报错
    
  • filter() - QuerySet (重点)

    #id=1
    User.objects.filter(pk=1) #返回一个QuerySet,其中是满足条件的数据
    User.objects.filter(name='zhj',age=18) #可以有多条数据,如果没有数据,就返回一个空白的QuerySet
    
  • exclude() -QuerySet (了解)

User.objects.exclude(pk=1) #和filter相反,取不满足条件的数据,返回QuerySet
User.objects.exclude(gender=True)# True/False 或者  1/0 和bool的filed比较
  • first() - 对象 (了解)

    User.objects.first() #获取QuerySet中的第一个元素,返回一个对象或None
    User.objects.all().first()
    User.objects.filter(xxxx).first()
    
  • last() - 对象 (了解)

    User.objects.last() #获取QuerySet中的最后一个元素,返回一个对象或None
    User.objects.filter(pk=1,name="zhj").last()
    
  • exists() - Boolean (了解)

    User.objects.exists() #User对应的表中是否有数据,判断数据表是否为空。返回True/False
    
  • count()

    User.objects.count() #数据数量
    User.objects.all().count() #数据数量 和上面等价
    User.objects.filter(age=18).count()
    
  • order_by() - QuerySet

    User.objects.all().order_by("pk") #根据id升序,返回QuerySet
    User.objects.all().order_by("age")
    User.objects.all().order_by("-age")#根据age 降序排列
    User.objects.all().order_by("age","name")
    User.objects.all().order_by("age","-name") #根据age升序,age相同的根据name倒序
    
  • QuerySet使用补充

    list(User.objects.all())  #转成list
    
    for e in User.objects.all(): #可以直接遍历
        print(e.age)#e=User对象
        print(e.name)
    
    if User.objects.filter(name="zhj"):#直接用于判断,QuerySet中没有数据时作为False使用
       print("至少有一个User数据")
    
    User.objects.all().query #query属性可以返回当前查询使用的sql语句
    

QuerySet限制操作

all() , filter() , exclude() , order_by() 都会返回QuerySet对象,QuerySet对象可以接受限制操作

User.objects.all()[0] #返回QuerySet中第一个对象
User.objects.filter(pk=1,name="zhj")[2] #返回QuerySet中第3个对象

uname="zhj" #可以使用变量在查询中
User.objects.filter(pk=1,name=uname)[2] #返回QuerySet中第3个对象

User.objects.exclude(pk=1,name="zhj")[1:3] #返回包含第[2,4)个对象(前闭后开)的新的QuerySet (子集)
User.objects.exclude(pk=1,name="zhj")[:3] # 等价于[0:3]
User.objects.exclude(pk=1,name="zhj")[2:] # 返回第3个到最后一个对象的新的QuerySet

条件查询

在众多查询方法中适合做条件查询的有:get()、filter()

get(pk=1,name=“zhj”) 如此只是等值查询,远不能满足查询需求

filter(pk=1,name=‘zhj’)

  • 比较

    不支持 "> < >= <= "这些符号,替换为关键字 lt、gt、lte、gte

    lt==less than

    gt==greater than

    lte==less than equal

    User.objects.filter(pk__lt=10) #id小于10 
    User.objects.get(pk__gt=100,age__lte=18) #id大于100,age小于等于18
    #日期比较也可以
    User.objects.exclude(birth__gte="2018-5-22 12:12:12") #yyyy-mm-dd HH:MM:SS格式
    User.objects.filter(birth__gt=datetime.now())
    
    dd = "2018-5-22 12:12:12"#使用变量
    User.objects.exclude(birth__gte=dd,name="xxx") #使用变量
    
  • 模糊查询

    • contains、icontains (iignore忽略大小写)

    • startswith、istartswith

    • endswith、iendswith

      User.objects.filter(name__contains="s") #name中包含"s"
      User.objects.filter(name__icontains="s") #name中包含"s"或"S" 忽略大小写
      
      User.objects.filter(name__startswith="s")
      User.objects.filter(name__istartswith="s")# name是以"s"或"S"开始 忽略大小写
      
      User.objects.filter(name__endswith="s")
      User.objects.filter(name__iendswith="s")# name是以"s"或"S"结尾 忽略大小写
      
  • 范围

    • in

    • range

      User.objects.filter(name__in=("zhj","zz")) # where name in("zhj","zz")
      User.objects.filter(age__in=(18,19,22))
      
      User.objects.filter(age__range=(18,20)) # where age between 18 and 20  [18,20]
      User.objects.filter(birth__range=('2018-05-10','2018-06-14'))
      
  • 空值:null

    • isnull

      User.objects.filter(age__isnull=True)
      
  • 日期(了解)

    • year

    • month 1-12

    • day

    • hour

    • minute

    • second

    • week 一年的第几周 1-52 or 53

    • week_day 周几 周日=1 周六=7

      User.objects.filter(birth__year="2018")
      User.objects.filter(birth__month="1") # 生日是1月 可选值1-12
      User.objects.filter(birth__week_day__gt=1) # 生日日期大于周日
      

映射查询

select * from t_user;查询全部列 User.objects.all()

select id,name from t_user;查询部分列–映射查询

  • values()
  • only()
#查询部分列(返回QuerySet,其中每个元素都是一个dict)
User.objects.all().values("pk") #等价上面写法:select id from t_user;
User.objects.filter(age__lt=20).values("name") # select name from t_user where age<20
#select id,age from t_user where name like "%zhj%"
User.objects.filter(name__contains="zhj").values("pk","age")
#返回, ] >
users = User.objects.only("pk","name") #会率先查询id和name,其他列暂时不查
                                       #即,QuerySet内的User中暂时只有id和name有值
#依然可以取到值
users[0].age #用到其他列数据时,才会查询它
#此处涉及到延缓查询的特性:lazy-load

聚合-aggregate

  • Avg()
  • Count()
  • Max()
  • Min()
  • Sum()
from django.db.models import Count, Max,Avg,Min,Sum
#返回 {'pk__max': 4, 'name__min': 'zhj'}
User.objects.aggregate(Max("pk"),Min('name'))#selecet max(id),min(name) from t_user;
#可以定义别名,影响返回值结构 -- 返回 {'mm': 4, 'name__min': 'zhj'}
User.objects.aggregate(Min('name'),mm = Max("pk"))#selecet max(id) as mm,min(name) from t_user;

分组-annotate

#select 分组条件列,聚合1,聚合2 from 表 group by 分组条件列

Models.objects.values(‘分组条件列’).annotate(聚合1,聚合2)

Models.objects.values(‘age’).annotate(聚合1,聚合2) == 对每种age都做聚合1 聚合2的统计

Model133.objects.values(“age2”).annotate(Max(“salary”),Min(“salary”))

select max(salary),min(salary),age2 from xx group by age2

#每种年龄中的用户的最大id和用户的最小生日
#select age,max(id),min(birth) from t_user group by age
User.objects.values("age").annotate(Max('pk'),Min('birth'))

#加别名:select age,max(id),min(birth) as 'mm' from t_user group by age
User.objects.values("age").annotate(Max('pk'),mm=Min('birth'))

#大于18的每种年龄中的用户的最大id和用户的最小生日
#加where : select age,max(id),min(birth) from t_user where age>18 group by age
User.objects.values("age").filter(age__gt=18).annotate(Max('pk'),Min('birth'))

#查询最大id大于3的每种年龄中 用户的最小生日,和最大id
#加having : select age,max(id),min(birth) from t_user group by age having max(id)>3
User.objects.values("age").annotate(Min('birth') ,ma = Max('id')).filter(ma__gt=3)

#加排序
User.objects.values("age").annotate(Min('birth'),mm=Max('pk')).order_by('mm')

F对象

User.objects.filter(id__gt=1) #id大于1

问题:查询id大于age的用户? 尝试:User.objects.filter(id__gt=“age”)–错误

当查询条件中需要另外的列时,可以使用F

User.objects.filter(id__gt=F(‘age’))–正确

from django.db.models import F
User.objects.filter(id__lt=F('age')) #id小于age
User.objects.filter(id__lt=F('age')+1) #id小于age+1
User.objects.filter(id__lt=F('age')+F('salary')) #id小于age+salary

Q对象

User.objects.filter(id__lt=1,name__contains=“zhj”) #id小于1 而且 name包含“zhj”

问题:查询id小于1 或 name含有“zhj”的数据?

​ 查询name不含有“zhj”的数据?

当需要 “或 (|) 非(~)” 逻辑时,可以使用Q

from django.db.models import Q
User.objects.filter(Q(id__gt=3)|Q(id__lt=2)) #id大于3 或 id小于2
User.objects.filter(Q(id__gt=3)|~Q(name__contains="zhj")) #id大于3 或 name不含有“zhj”
User.objects.filter(Q(id__lt=3)|Q(age__gt=F("id")*10)) #id小于3 或 age大于id*10

User.objects.filter(Q(id__gt=3)|Q(name__contains="zhj"),age__lt=2) #id大于3 或 name包含"zhj" 且 age小于2

User.objects.filter(Q(id__gt=3)|Q(name__contains="zhj")~Q(age__lt=2) #id大于3 或 name包含"zhj" 且 age不小于2

select xxxx,xxx,xxxx from xx where xxx group by xx having order by

原生sql的支持

直接暴露的Model底层封装的Connection和Cursor

from django.db import connection
with connection.cursor() as cursor:
    cursor.execute("SELECT age,name FROM t_user WHERE baz = %s", [self.baz])
    #返回tuple of tuple
    result = cursor.fetchall() #(("zhj",18),(..),)


with connection.cursor() as cursor:
    cursor.execute("SELECT age,name FROM t_user WHERE baz = %s", [self.baz])
    #columns = ["age","name"]
    #row=(1,"zhj")   
    #zip(columns,row) = [("age",1),("name","zhj")]
    #dict(zip(columns,row)) = {"age":1,"name":"zhj"}
    columns = [col[0] for col in cursor.description] 
    #返回 list of dict : [{"age":1,"name":"zhj"},{...}]
    result = [dict(zip(columns, row)) for row in cursor.fetchall()]

Model

关联关系

有,从属的关系

数据库的表之间都是有联系的,正如我们之前讲的,有关联关系的存在

关联关系的种类:1对1 , 1对多 , 多对多

在Model中体现关联关系,进而更好的在多表之间操作数据

  • ForeignKey(to=关系对方的类或类名或‘self’,on_delete=级联选项)
  • OneToOneFiled(to=关系对方的类或类名或‘self’,on_delete=级联选项)
  • ManyToManyFiled(to=关系对方的类或类名或‘self’)

关联关系搭建

1:*
class Category(models.Model):
    ctitle = models.CharField(max_length=20)
    create_time = models.DateField(auto_now_add=True)
    class Meta:
        db_table="t_category"

class Goods(models.Model):
    gtitle = models.CharField(max_length=20,unique=True)
    gprice = models.FloatField(db_column='price')
    #to=关系对方Model   ForeignKey=一对多关系,并且当前方为多
    #关系属性:对应外键列:db_column="cate_id"
    cate9 = models.ForeignKey(to=Category,on_delete=models.CASCADE,db_column="cate_id")
    #关系属性:对应外键列:cate_id == 关系属性名_id
    #cate = models.ForeignKey(to=Category, on_delete=models.CASCADE)
    class Meta:
        db_table = "t_goods"
1:1
class Passport(models.Model):
    note = models.CharField(max_length=20)
    person = models.OneToOneField(to="Person",on_delete=models.CASCADE) #关系属性
    class Meta:
        db_table="t_passport"

class Person(models.Model):
    name = models.CharField(max_length=20)
    age = models.IntegerField()
    class Meta:
        db_table="t_person"
*:*
class Student(models.Model):
    name = models.CharField(max_length=20)
    age = models.IntegerField
    class Meta:
        db_table="t_student"
class Course(models.Model):
    title = models.CharField(max_length=30)
    expire = models.SmallIntegerField()
    students = models.ManyToManyField(to=Student) #关系属性
    class Meta:
        db_table="t_course"

查询

1对多关系   Category(1):没有关系属性   Goods(*):其中有关系属性cate9
#单独查一方:Category
cs = Category.objects.all()
cs[0].goods_set # 没有多方的关系属性,通过"多方类名_set"获得对方的多个数据
                # 此时只是返回一个RelatedManger,尚未查询,需要如下调用all或filter等查询方法
cs[0].goods_set.filter(gprice__gt=200) #第一个类别中价格大于200的所有商品 (如此才有值)
cs[0].goods_set.all() # 第一个类别的所有商品(如此才有值)

#单独查询一方:Goods
gs = Goods.objects.all()
gs[0].cate9 #通过关系属性,获得商品的类别对象
gs[0].cate9.ctitle #获得对象后 自然可以继续获取属性

#关联查询:以类别为条件查询商品(获取对方用关系属性,获取对方的属性获用【__】)
Goods.objects.filter(cate9__pk__gte=1) # 类别id大于1的商品
Goods.objects.filter(cate9__ctitle__contains="s")# 列名标题含有"s"的商品

#关联查询:以商品为条件查询类别(获取对方用对方小写类名,获取对方的属性获用【__】)
Category.objects.filter(goods__pk__gt=1)#id大于1的商品的类别
Category.objects.filter(goods__gtitle__contains="a")#标题中含有"a"的商品的类别

#关联查询:保留双方数据(获取对方的属性获用【__】)
Goods.objects.filter(cate9__ctitle__contains="s").values("cate9__ctitle","pk","gprice")

#注意在进行联合查询时,可能会由重复数据出现,解决:
list(set(Category.objects.filter(goods__gprice__gt=200)))
多对多关系   Course:有对方关系属性students   Student:没有对方关系属性 
#查询一方:Student
stus = Student.objects.all()
stus[0].course_set #返回ManyRelatedManager,但并未查询数据
stus[0].course_set.all() #获得对方所有数据
stus[0].course_set.filter(title__contains='s') #获得第一个学生的 标题中含有"s"的课程
#查询一方:Course
cours = Course.objects.all()
cours[0].students.all()
cours[0].students.filter(age__gt=18)

#关联查询:以课程为条件 查询学生
Student.objects.filter(course__title__contains="h") #标题中含有"h"的课程中的学生
Student.objects.filter(course__expire__gt=2) #课时大于2的课程中的学生
#关联查询:以学生为条件 查询课程
Course.objects.filter(students__name="zzz") #姓名为zzz的学生参加的课程
Course.objects.filter(students__age__gt=18) #年龄大于18的学生参加的课程

#关联查询:保留双方数据
Course.objects.filter(students__name="zzz").values("title","expire","students__name")
11关系: Person:没有关系属性   Passport:有关系属性person
#单独查一方:Person
per = Person.objects.all()
pass[0].person  #通过关系属性名 获取关系对方
per[0].passport #没有关系属性时,通过关系对方全小写的类名,获得对方
#单独查一方:Passport
pass = Passport.objects.all()
pass[0].person.name #继续获取属性值
per[0].passport.note #继续获取属性值

#关联查询
Person.objects.filter(passport__note__contains="a")
Person.objects.filter(passport__note="a") 
Person.objects.filter(passport__pk=1)
#关联查询
Passport.objects.filter(person__pk=1)#id为1的person的Passport
Passport.objects.filter(person__pk__gt=1)#id大于1的person的Passport
Passport.objects.filter(person__name__contains="z")#id大于1的person的Passport

增加

单独增加主表方(没有外键一方) 和单表增加无差异

Category(title="男装",...).save()

为已存在的主表方附加从表数据**(常见的增加情形)**

c = Category.objects.get(pk=1)
#goods_set = RelatedManager
c.goods_set.create(title="zzz6",price=100) #不需要再save

或

c = Category.objects.get(pk=1)
g=Goods(title="zzz6",price=100,cate9=c)#为商品的关系属性赋值
g.save() #需要save
#注意,如果是1:1 则只能用第二种

同时增加主从数据

g = Goods(title="zzz6",price=100)
c = Category(title="类别1",note="xx")
g.save() #此时,外键的值为null
c.save()
c.goods_set.add(g) #会同步数据库,补充外键值

或

c = Category(title="类别1",note="xx")
c.save()
g = Goods(title="zzz6",price=100,cate9=c)
g.save() #此时,good是有外键值的
#注意如果是1:1只能用第二种

删除

单独删除从表方,和删除单表无差异

Goods.objects.get(pk=1).delete()

删除主表方,此时要看从表方的级联设置,会影响到从表方

级联选项 : 在关联关系中删除主表时,对于从表可以有级联操作

  • CASCADE 级联删除
  • SET_NULL 外键置空(如果允许空的话)
  • PROTECT 不允许直接删除主表
  • SET_DEFAULT 需要为外键列设置默认值,默认值也应该是合法的外键值,可以在表中预留一个id
  • SET 将外键设置为某个值,值应该是合法的外键值,可以在表中预留一个id
  • DO_NOTING dango什么也不做,由数据库决定是否合法

1:1 支持 CASCADE SET_NULL(前提:外键列允许为null) DO_NOTION

1:* 都支持

* 不支持级联删除

 #如果要删除主,所有从的外键置null  (重要)
 per = models.OneToOneField(to=Person,on_delete=models.SET_NULL,null=True)
 #如果要删除主,所有从一起删除
 per = models.OneToOneField(to=Person,on_delete=models.CASCADE,null=True)
 #如果要删除主,django什么也不做,有数据库决定是否合法
 per = models.OneToOneField(to=Person,on_delete=models.DO_NOTHING,null=True)
 #如果要删除主,所有从的外键置为6  (重要)
 cate115 = models.ForeignKey(to="Category",on_delete=models.SET(6))
 #如果要删除主,所有从的外键置为默认值5  (重要)
 cate115 = models.ForeignKey(to="Category",on_delete=models.SET_DEFALUT,default="6")
 #如果要删除主,如果有从数据存在,不允许删除
 cate115 = models.ForeignKey(to="Category",on_delete=models.PROTECT)

修改

查询出数据,修改属性,然后save()即可,和单表跟新无任何差异

cs = Category.objects.all()
c = cs[0]
c.title="new_title"
c.save()

坑:
Category.objects.all()[0].title="new_title"  #查询一次,并修改
Category.objects.all()[0].save() #查询一次,并保存(不能更新任何数据)

QuerySet 高级特性:懒加载 Lazy-load

QuerySet在查询数据时,是延迟执行,直到真正使用了数据,才发起查询

a = Person.objects.all() #未查询
b = Category.objects.all() #未查询
print(a) #使用了a,则查询a
#如上代码只执行了一次查询

c = Category.objects.all() #未查询
print(c) #使用了c,查询c
goods = c[0].goods_set.all() #未查询goods
print(goods) #使用了goods,查询goods

测试方式

找到mysql的安装目录下的my.ini文件,添加配置:

[mysqld]
.....
log = "E:/mysql_log.sql" #设置日志文件,记录sql语句执行
或
general-log=1
general_log_file=E:/mysql_log.sql

net stop mysql

net start mysql

事务

django的事务管理:代码块内没有异常则事务提交,如果出现异常则事务回滚

from django.db import transaction
with transaction.atomic():
    Person(name="tx_name22", age=18).save()
    Person(name="tx_name33", age=18).save()
    # Person(name="tx_name4", age="abc").save() #异常,此时事务回滚
    # a = 10 / 0 #异常,此时事务回滚
    # raise ValueError("xx") #异常,此时事务回滚

控制事务,并处理其中异常

可以捕获事务内发生的异常,注意要在事务外部捕获;
如果在事务内部捕获,而没有抛出异常的话,会改变异常的正常行为。
import traceback
try:
    with transaction.atomic():
        Person(name="tx_name22", age=18).save()
        Person(name="tx_name33", age=18).save()
        Person(name="tx_name4", age="zzz").save()
        # a = 10 / 0
        # raise ValueError
except ValueError as e:
   print("exept~~")
   traceback.print_exc()#打印异常栈信息

便捷事务控制,以函数为整体,函数内如果出现异常则回滚,否则提交(了解)

@transaction.atomic
def abc():
    Person(name="tx_name2211", age=18).save()
    Person(name="tx_name3311", age=18).save()
    Person(name="tx_name4", age="zzz").save()
    # a = 10 / 0
    # raise ValueError
abc()
1.知识点练习 2遍
2.完善ems,在login和register中引入model

##请求状态保持

Django 框架学习基础笔记_第8张图片
Django 框架学习基础笔记_第9张图片
Django 框架学习基础笔记_第10张图片

  • 如上两个View分别接收各自的请求,当test1中的请求数据,test2中是无法使用的
  • Http协议请求,无状态;
  • 请求内的数据:请求参数,逻辑运算得到的数据等,都会随着请求的结束而销毁
  • 实际的业务需求中,有很多数据需要在多个请求之间共享。所以需要保持数据。
  • 数据保持的手段:cookie,session

Cookie

小甜品,饼干…

由服务器生成,存储在浏览器客户端的一小块数据 ( 有长度限制,不适合存储大量数据 )

用于持续保持用户的状态,常用于做用户状态的保持,常见的"记住我",“自动登录” 即是。

Django 框架学习基础笔记_第11张图片

使用过程-写cookie

写出cookie是HttpResponse的功能

为Response设置好cookie后,在响应到客户端时,cookie会随之存入客户端

cookie的数据格式: [key:value]

def write9(request):
    #1.数据
    name = User.objects.get(pk=2).name
    #2.将数据存入Response
    res = HttpResponse("hello this is test1~~")
    rese.set_cookie("realname",name)
    #3.响应
    return res

def write9(request):
    #获得重定向时的响应对象
    res = redirect("/test/hello2/")
    #在Response对象中设置cookie数据
    res.set_cookie("name","zhj") #key="name"  value="zhj"
    res.set_cookie("age",18) #key="age"  value=18
    return res #响应

def write99(req):
    #获得转发时的响应对象
    res = render(req,"test.html")
    #在Response对象中设置cookie数据
    res.set_cookie("name","zhj") #key="name"  value="zhj"
    res.set_cookie("age",18) #key="age"  value=18
    return res #响应
#cookie的生命周期设置
res.set_cookie("password","123") #存活一次会话,浏览器关闭时,失效
res.set_cookie("password","123",max_age=100)#cookie存活100s
res.set_cookie("password",max_age=0)#cookie存活0s,生命结束,删除cookie

Django 框架学习基础笔记_第12张图片

使用过程-读cookie

请求服务器时,当再次访问项目时,会携带本项目的所有cookie到达服务器,通过request对象可以读取cookie中的数据

def hello3(req):
    #返回所有cookie
    print(req.COOKIES) #{'password': '123', 'password2': '456'}
    print(type(req.COOKIES)) #dict
    req.COOKIES['password'] #"123"
    req.COOKIES.get("password") # "123"

补充:cookie 中文问题

cookie中不允许存中文,如果有,需要对中文做编码处理

#写cookie
res.set_cookie("password", str("臧红久".encode("utf-8"),'latin-1'))

#读cookie
str(req.COOKIES['password'].encode('latin-1'),"utf-8")

测试实例:记住我

Session

保持请求状态,由服务器生成,且存储于服务器

用作多个请求之间,共享数据

Session使用

####启用Session功能

settings.py中已有如下设置

保障session可以使用

INSTALLED_APPS = [
    ...
    'django.contrib.sessions',
    ...
]
MIDDLEWARE = [
    ...
    'django.contrib.sessions.middleware.SessionMiddleware',
    ...
]

在使用session前,需要为django.contrib.sessions做移植 生成数据表,用于存储session数据

python manage.py migrate #创建 “django-session”表

Session生命周期

默认存活两周

可以修改为一个会话周期,在settings.py中:SESSION_EXPIRE_AT_BROWSER_CLOSE=True

写Session

#注意,django默认的session存储位置为数据库的django_session表,需要通过python manage.py migrate生成
def xxx(req):
    req.session['username']="臧红久"
    req.session['login']=1
    ...

读Session

def xxx(req):
    print(req.session['username'])
    print(req.session.get('login'))
    ...

注意session没有中文问题,没有长度限制,相比存在客户端的cookie更安全

清除Session

req.session.flush() #清除数据,置空cookie,清除数据表中的记录
req.session.clear() #清除数据
del req.session['username'] #清除一个key的数据

Session存储位置

  • 数据库:INSTALLED_APPS 中的 django.contrib.sessions 会在执行移植文件时生成数据表(默认)
  • 缓存:为Project设置缓存组件,则session可以存入缓存,提高执行效率(redis)
  • 文件:存于服务器本地文件中(不建议)
  • cookie:存取客户端的cookie中(不建议,适合小项目)

Session实现原理(session和cookie的联系)

Django 框架学习基础笔记_第13张图片

如上图:session创建时有自己的ID,且ID随着响应存入浏览器,在一个cookie中。

后续请求时,服务器可以从cookie中获取session的ID,进而获取对应session

测试实例:多请求共享用户名

Cookie Session选择

  • 需要在多个请求间多次共享使用的数据,保持状态,使用session,如在多个请求间持续保持一些数据
  • 需要在一段时间后依然可以保持的小块数据,使用cookie,如“记住我”,保证一段时间后,可以自动登录
  • 每个网站在一个浏览器中的cookie数据上限是4k,session没限制
  • cookie存在浏览器本地,隐私性不好。安全性较低

全局错误视图设置

http请求的状态码 200=成功 404=资源未找到 500=服务器错误 400=bad request… 403=请求拒绝

可以为常见的错误定制错误页面

1> 关闭调试模式,设置allow_host

2> 在templates下新建:404.html 500.html 400.html,

3> 在出现对应错误时,会自动跳转对应错误页面

事务控制

def testtx3(request):
    try:
        with transaction.atomic():#开始一个事务 环境,with结束时,如果没有异常,事务提交;否则回滚
            Person(name="tx_name22", age=18).save()
            Person(name="tx_name33", age=18).save()
            a=10/0
        return render(request,"success.html")
    except:
        traceback.print_exc() #打印异常栈
        print("error") #此时的异常已经回滚
        return render(request,"error.html") #还可以为错误提供专用的视图页面

基于View的事务控制(了解)

设置ATOMIC_REQUESTS,则per-view-per-transaction

在view开始前开启事务,如果response成功构建,则提交事务
在response之前如果出现异常,则回滚事务

DATABASES = {

    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'db9',
        ...
        'ATOMIC_REQUESTS':True,
    }
}
#运行View前已经开启事务
def testtx2(r):
    Person(name="tx_name22", age=18).save()
    Person(name="tx_name33", age=18).save()
    a=10/0 #出现异常,事务自动回滚
    return HttpResponse("abc")
	#view结束,没有异常,事务提交

坑:如下情况,事务依然提交

则,基于View的事务控制,不能处理异常,否则事务行为被破坏

如果在view中try了异常,django就认为整个View没有异常,则事务正常提交

def testtx(request):
    try:
        Person(name="tx_name22", age=18).save()
        Person(name="tx_name33", age=18).save()
        a=10/0
    except Exception:
        print("error")
        return render(request,"error1.html")
    return render(request,"success.html")

作业

# 技术点熟悉,练习
# 登录  注册
# 注册后自动跳转登录页面
# 登录后记住我 一周  -- cookie
# 登陆后 在后续的请求中 显示用户真实姓名
# 强制登录  -- sesion

Template

##Template概述

模板格式:html文件

模板作用:生成html,作为响应内容 (更好的定制响应内容)

return HttpRespones("hello")

return render(request,"xx.html")

模板运行方式

模板不会独立运行,而是在View之后会跳转一个模板

def xx(request):
    print("hello")
    #跳转Template
	return render(request,"abc.html")
abc.html

    {# 静态内容 #}
    go
    
    {# 动态内容 #}
    {{ request.session.username }}
    {{ request.COOKIES.name }}
    
{{username}}
{{realname}}

模板的搜索根目录:Project下的templates目录 和 “已安装App”下的templates目录

模板文件管理方式: 1> 每个App自己持有,且有自己的独立目录防止多个App中的模板命名冲突

​ 2> 统一放在Project中,且有自己的独立目录防止多个App中的模板命名冲突 (建议)

模板渲染

即从View跳转到Template后,Template的执行过程渲染过程静态内容原样输出,动态内容逻辑运算

渲染结果:静态内容 + 动态渲染代码运算结果

tem = loader.get_template("user/abc.html") #到模板根目录查找abc.html
result = tem.render(request=request) #模板渲染,返回【静态+动态的运算结果】!!!
return HttpResponse(result) #将模板渲染结果响应给client
return render(request,"user/abc.html") #等价如上过程

DTL

模板语言:Django内置有自己的模板语言(模板引擎);第三方的模板语言比较流行的有jinja

​ Django官方表示,如果没有不得不换的原因,推荐内置模板语言

Django Template Language(DTL)

模板文件中,包含的动态渲染代码,使用的就是DTL {{xxx}}

模板语言安装: settings.py

Django 框架学习基础笔记_第14张图片

传值

模板的正确使用,是衔接View,接收View传递的数据,将数据渲染在html网页中

值:View中通过Model组织得到的数据

传递:由View 传递给 Template

目标:将数据渲染到一块静态内容(html)中 , 定制动态内容,作为请求的响应内容

传值示例:{key:value,key2:value2}

def xx(request):
    user = User.objects.get(pk=1) #获得数据
    # 跳转abc.html,并向其传值:{"user":user},abc.html就可以使用如此数据
	return render(request,"abc.html",{"user":user})
abc.html
  
	...
    hello,{{user}} 
    ...

变量-取值

从本节开始,讲解DTL的详细语法

变量,是要讲解,在模板中如何去获取View传递来的数据

简单数据

View : render(...,{"name":"zhj","id":18})
Template:
    {{ id }}
    <input type="text" name="" id="" value="{{ id }}">
    <span style="color:red">{{ id }}</span>
    <a href="/tempate/test2/?id={{ id }}">{{ id }}</a>
    <script>
        var id = "{{ id }}";
    </script>

Model对象 (重点)

View:render(...,{"user":User(id=1,name="zhj",age=18,order=Order(price=100.5))})
     render(...,{"user2":User.objects.get(pk=1)})
     render(...,{"user3":User.objects.filter(pk=1)})
Template:{{user}} #获取User对象
		{{user.id}} #User对象的id属性
		{{user.name}} #User对象的name属性
		{{user.age}} #User对象的age属性
		{{user.order.price}} #User对象的order属性的price属性
         {{user2.order.price}} #User对象的order属性的price属性
         {{user2.order_set}} #返回一个RelatedManger
         {{user2.order_set.all}} #RelateManager.all() ,返回一个QuerySet
		{{user2.order_set.all.0}} #返会第一个Order
         {{user3.0.age}} #User对象的order属性的price属性

列表 (了解)

View: render(...,{"list":[12,34,56]})
Template:{{list}}
         {{list.0}} #list[0]
         {{list.1}} #list[1]

dict (了解)

View:render(...,{"dict":{"name":"zhj","age":18}})
Template:{{dict.name}} #dict['name']
    	 {{dict.age}}  #dict['age']

方法 : 无参方法 (了解)

class User:
	def abc():
		return "abc"
View:render(...,{"user":User(...)})
Template:{{user.abc}} {# 输出方法的返回值 #}

则如上,变量中**"."** 是一个很强大的字符 “对象属性,列表下标,字典的key,对象的方法”

分支判断:if 标签

语法格式

{# if #}
{% if user.id > 1%}
	<input type="text"/> {# 如果if成立则输出input #}
{% endif %}

{# 0/非0  None/非None ""/非""  False/True  空QuerySet/非空QuerySet#}
{% if vvv %}
	<input type="text"/> {# 如果if成立则输出input #}
{% endif %}
{# if ..elif ..#}
{% if user.id > 1%}
	<input type="text"/>   {# 如果if成立则输出input #}
{% elif user.abc == "thisisfn" %}
	this is fn             {# 如果if成立则输出这句平文 #}
{% endif %}
{# if ..elif ..else#}
{% if user.id > 1%}
	<input type="text"/>   {# 如果if成立则输出input #}
{% elif user.abc == "thisisfn" %}
	this is fn             {# 如果if成立则输出这句平文 #}
{% else %}
	<p>这是一个段落</p>     {# 如果以上都不满足输出一个p标签 #}
{% endif %}

运算

{# 比较运算 == != >= <=  >  < #}
{% if name == "zhj" %} 
{% if id >= 10 %} 
{# 逻辑运算 #}
{% if 1 or 2 %} {# and or not 与或非 拼接多个条件 #}
    ...
{% elif id > 100 and not name %}
    ....
{% elif not id > 100 and not name %}
    ....
{% endif %}
{# in / not in  包含于/不包含于   用于字符串 和 序列 和 dict 和 QuerySet#}
{% if  user.name not in "aazhjzzzaaa" and users.0 in users%}
    name in zhjasdf
{% endif %}

注意:if中的表达式 如 name == “zhj” 中 “name” “==” “zhj” 三个部分之间要用空格分开

遍历:for 标签

for标签中,可以选择添加一个empty子标签,在数据不存在时执行

{% for user115 in users%} {# {% for user in users reversed%} 倒叙遍历 #}
    {{user115.age}} {# {{..}} 获取遍历中的数据 #}
    {{user.name}}
    {% empty %}
        <p> 如果遍历的数据为[],或不存在,执行empty,输出这个段落</p>
{% endfor %}
      
<table>
 ...
 <tbody>
        {% for g in goods4 %}
            <tr>
            <td>{{g.id}}</td>
            <td>{{ g.title }}</td>
            <td>{{ g.price }}</td>
            </tr>
            {% empty %}
        		<tr><td colspan="3" align="center">没有数据</td></tr>
        {% endfor %}
</tbody>
</table>
{% for a,b in dict9.items %} {# 遍历字典  dict.items #} (了解)
    {{ a }} {{ b }}
    {% empty %}
        <p> 如果遍历的数据为[],或不存在,执行empty,输出这个段落</p>
{% endfor %}
{% for a,b in ll9 %} {# 遍历list of list #} [[1,2],['a','c']] (了解)
    {{ a }} {{ b }}--
{% endfor %}
{% for ... %}
	...
    {{ forloop.counter0 }}  遍历索引从0开始
    {{ forloop.counter }}   遍历索引从1开始
    {{ forloop.revcounter }}  倒序索引
    {{ forloop.revcounter0 }}
    {{ forloop.first }}  是否为第一次遍历
    {{ forloop.last }} 
    ...
{% endfor %}

{% for ... %}
   ...
   {% if  forloop.first %}
		第一次遍历
   {% else %}
   ...
{% endfor %}

补充:{% now “Y-m-d H:i:s”%} 获取当前的服务器时间,注意时区问题

过滤器(非重点)

获取值的过程中,可以对值进行处理,通过 过滤器 完成

{{ xx }} 正常取值

{{ xx|过滤器 }}

{{birth|date:'Y/m/d H:i:s'}}

{{name|length}}  {#获取长度#}
{{list|length}}  
{{dict|length}}
{{queryset|length}}
{{name|length_is:'4'}} 

{{name|lower}}  {# 所有字符变小写 #} (了解)
{{name|upper}}  (了解)
{{name|default:'zhj'}} {# 没值、False、None、""、0 #}(了解)

{{ list115.1|add:'1' }} {# 加法 #}
{{ list115.1|add:'-1' }} {# 减法 #}
{{ list115.1|divisibleby:'2' }}
{# % 求模运算 #} {% widthratio 5 10 100 %} {# a/b*c 5/10*100 百分比#} {% widthratio 5 1 2 %} {% widthratio list115.1 1 list115.2 %} {# 乘法运算 #} {% widthratio list115.1 list115.2 1 %} {# 除法运算 #}
#所有的过滤器,也可以用在标签中(if for)
{% if  user.birth|date:'Y/m/d H:i:s'|length > 10%}
    <p>length gt 10</p>
{% endif %}

模板继承

项目的多个页面中会有重复的部分,会导致在多个模板文件中会有重复的代码

此时,会造成代码冗余,不利于项目的管理

模板继承,可以很好的解决这种问题

{% extends "fruit_ex/super1.html" %}
{% block content_super1 %}
{% endblock %}

定义父类模板,其中定义多个模板中都重复的内容

不重复的位置用如下标签占位:

{% block super1%}
{% endblock %}

block块 之间的内容是不重复的部分,由子模板自己定制

block块 之外的其他内容,子类模板直接继承

base.html
...
<body>
    <div style="background-color:lavender;margin-bottom: 100px">
        欢迎你,{{ user.name }}
    </div>
    {% block super1%}{% endblock %}
    <div style="font-size: 12px;color:#999;margin:0 auto;text-align: center;padding:20px 0">
        Copyright © 2013-2018
        <strong><a href="#" target="_blank">百知</a></strong>&nbsp;
        <strong><a href="#" target="_blank">baizhi.com</a></strong> All Rights Reserved. 
    </div>
</body>

继承父模板

{% extends “base.html” %} 在页面第一行定义继承逻辑

子模板中只能定义父模板中 block块中的内容,其他内容都是继承自父模板

则实现了子模板中不用定义父模板中已经定义的内容,进而消除重复

sub.html
{% extends "base.html" %}
{% block super1 %} {!-- 覆写父模板block --}
    <div style="height: 100px;background-color: #999999">
        这是子类模板自己的内容
    </div>
{% endblock %}

坑:在子模板中,只有block块内的内容,才是有效的内容,block块外的内容,不被解析,视为无效

反向解析

访问路径硬编码

如下在模板或View中 访问View时,将View的访问路径是硬编码

则当view的路径配置改变时,维护成本会很高!

urlpatterns = [
    path('admin/', admin.site.urls),
    path('test/',include("app1.urls")),
]
urlpatterns=[
    path('a/',views.testconf1)
]
template:<a href="/test/a/">...</a>  <form action="/test/a/"..
view: redirect("/test/a/")

使用反向解析

1> 除了给View访问路径外,额外给View一个命名

2> 需要访问View的时候,不再直接写View的访问路径,而是写View的命名

urlpatterns=[
    path('a/',views.testconf1,name="zhj") #name=为view命名
]
#使用View命名访问View
template:<a href="{% url 'zhj'%}">ssss</a>  #返回ssss
view: redirect("zhj") #重定向中会自动反向解析路径, 跳转:/test/a/

如上代码无论View的路径如何改变,代码的维护成本都很低

反向解析管理

如果多个App中有相同命名的View,则自然会有View的命名冲突!

管理:保证每个APP都有自己的独立命名,如下:

#1.全局的urls
urlpatterns=[
     path('admin/',admin.site.urls),
     # 为app1 定义独立命名:"user"
     path('aa/',include(('app1.urls','app11'))# 为app2 定义独立命名:"order"
     path('bb/',include(('app2.urls','app22'))]
#2.App1/urls.py
urlpatterns=[
    path('a/',views.xxx,name="query") #相同的命名:query
]
#3.App2/urls.py
urlpatterns=[
    path('b/',views.xxx,name="query") #相同的命名:query
]
          
#4.使用View命名访问View  ("一级命名:二级命名")
template:<a href="{% url 'app11:query'%}">ssss</a>   #http://.../aa/a/
          <a href="{% url 'app22:query'%}">ssss</a>   #http://.../bb/b/
view: redirect("app11:query") #重定向中会自动反向解析路径, 跳转:/test/a/
      redirect("app11:query") #重定向中会自动反向解析路径, 跳转:/test/a/

进一步管理

一级命名下有多个二级命名

二级命名下分为多个三级命名

三级命名对应具体的View

urlpatterns=[
     path('admin/',admin.site.urls),
     path('aa/',include(('app1.urls','app11')) #一级命名
]
#In App1/urls.py
urlpatterns=[
    ...
    path('query/',include(([path('one/',views.testconf1,name="one9"),#三级命名
                            path('all/',views.testconf8,name="all9")
                           ],
                           "query9") #进一步独立命名(二级命名)
                         )
    ),
    ...
]
template:<a href="{% url 'app11:query9:one9'%}">ssss</a>  #http://.../aa/query/one/
view:redirect("app11:query9:one9")#跳转 /aa/query/one/

补充:命名路径的反向解析

urls.py
    path("hello///", views.hello, name="a"),
    re_path(r'^hello2/(?P\w+)/$', views.hello2, name="b"),
Views:
   redirect("a") 错误!!!
    redirect("a",age=18,name="zhj") #/hello/18/zhj/
   redirect("a",age=19,name="zwl") #/hello/19/zwl/
    redirect("b",name="zhj") #/hello2/zhj/
Template:
   {% url "a" %} 错误!!
    {% url "a" "19" "zhj" %} #/hello/19/zhj/
    {% url "b" "zhj"%} #/hello2/zhj/
作业:
1.技术点
2.ems:显示所有员工,删除员工,添加员工,更新员工(选作)
要求:两个APP
      login/register  APP(admin)  t_admin
      员工操作        APP(employee) t_employee

验证码

在常规的Form表单使用中,验证码是常用的组件,用于更好的保障请求的合法性,防止无效访问,恶意访问,暴力破解等攻击

在服务器端,生成一个随机的code:“aecd1” ,将code画到一张图片中,最终将图片写出给client

注意:依赖第三方包:pillow

pip install pillow

开发过程-1

导入第三方验证码工具:

在这里插入图片描述

将文件拷贝到自己的项目中:2个py文件,1个data文件夹,保证三个处于项目中的同一个目录中

开发过程-2

编码,生成验证码,输出给客户端浏览器

import random,string
def getcaptcha(request):
    #从image.py中导入ImageCaptchar类
    from xx.image import ImageCaptcha
    #为验证码设置字体 获取当前目录下的xxx目录下的segoesc.ttf文件
    image = ImageCaptcha(fonts=[os.path.abspath("xxx/segoesc.ttf")])
    #随机码
    #大小写英文字母+数字,随机抽取5位作为验证码 ['x','x','x','x','x']
    code_list = random.sample(string.ascii_lowercase+string.ascii_uppercase+string.digits,5) 
    code = "".join(code_list)#获得随机的5为验证码
    #将验证码存入session,以备后续验证
    request.session['code']=code
    #将生成的随机字符拼接成字符串,作为验证码图片中的文本
    data = image.generate(code)
    #写出验证图片 给客户端
    return HttpResponse(data,"image/png")

开发过程-3

在模板中定义显示验证码的img标签

Tem中:
	<script>
            function refresh(){
                //重新为img的src赋值,将触发一次图片的请求
                $("#num").attr("src","/cap/test/?"+new Date().getTime())
            }
    </script>
	<form>
        ....
        <!--用户在此输入验证码-->
        <input type="text" name="code"/>
        <!--请求 开发过程-2中的view,获得输出的验证码图片-->
        <img id="num" src="{% url "xxx" %}" style="width: 70px" onclick="refresh();"/>
        ....
    </form>

开发过程-4

开发过程-3 中的form,应该请求此View函数

验证用户输入的验证码是否正确

def login(request):
    #从session中获取真实的验证码
    rcode = request.session.get("code")
    #接收"开发过程-3"表单中用户输入的验证码
    code = request.POST['code'] 
    if rcode.lower() == code.lower():#验证是否输入正确
        #执行登录验证逻辑
    else:
        #再次跳回到登录页面

文件上传

上传:将客户端的文件传送给服务

实例:用户注册中,头像上传

设计:将上传来的文件存在服务器本地目录中,将文件的存放目录路径存于t_user表中(ImageField)

注意:ImageField依赖第三方包:pillow

开发过程-1

为用户定义form表单,选择要上传的文件

**注意:method=“post” enctype=“multipart/form-data” **

<form action="{% url "upload" %}" method="post" enctype="multipart/form-data">
    {% csrf_token %}
    <input type="text" name="name"/>
    <input type="file" name="source"/>
    <input tppe="submit" value="shangc~"/>
</form>

开发过程-2

接收form发来的数据 (文件+其他数据)

def upload9(request):
    name = request.POST.get("name") 
    file = request.FILES.get('source') #接收文件
    print("文件原始名为:",file.name," 文件类型为:",file.content_type)
    return HttpResponse("提交成功")

开发过程-3

保存数据(文件+其他参数)

1>将文件存储到服务器本地目录中,并将文件的保存路径存储到数据库中

2>其他参数存储到数据库中

通过Model完成保存任务!

3.1 定义Model

用于完成上传数据的存储

class User(models.Model):
    name = models.CharField(max_length=20)
    pic = models.ImageField(upload_to="user_pic")
    class Meta:
        db_table = "t_user"
#有了Model后,要有对应的库表,可以自己创建,也可以移植。

注意ImageFiled,会帮助完成文件的保存,所以它需要知道文件该存到哪个目录:

​ 1> settings.py中:MEDIA_ROOT = os.path.join(BASE_DIR,"media9") == 项目根目录下的media9目录

​ 2> ImageFiled中:upload_to="user_pic"

如此上传来的文件,会被ImageField存储在media9目录下的user_pic目录下

3.2 通过Model,存储上传数据

Model.save() 即可

def upload9(request):
    name = request.POST.get("name") 
    file = request.FILES.get('source') #接收文件
    #将收到的参数存入Model中
    user = User(name=name,pic=file)
    #保存User对象,此时会自动将文件存入指定目录,并将文件保存路径存入数据表,其他上传的参数也存入数据库
    user.save()
    ...

upload-db
[**upload**]

注意,此时数据库中存储的路径是相对于MEDIA_ROOT的路径

开发过程-4

回显图片

1> 首先将MEDIA_ROOT目录,设置为静态资源根目录,以便于访问到上传的图片

2> 通过ImageFiled的url属性,可以获得图片的访问路径(相对于静态资源根目录的 相对路径)

#查询到用户信息
user = User.objects.filter(pk=1)
return render(request,"xx.html",{"user":user}) #user传给模板

<!-- 模板中显示   user.pic.url 即数据库中存储的文件路径 -->
<img src="/static/{{user.pic.url}}"/><img src="{% static user.pic.url  %}"/>


{%static 'a/b/c/aa.jpg'%}==> /static/a/b/c/aa.jpg
<img src="{% static user.pic.url  %}"/> ==> /static/a/c/d/c.jpg

补充

python原生文件操作代码

def test_upload2(r):
    import os,uuid
    b = r.FILES.get("source")  # 获取上传的文件
    unique_name = str(uuid.uuid4())  # 唯一文件名
    ext = os.path.splitext(b.name)[1]  # 文件后缀
    name = unique_name + ext  # 拼接文件名
    with open(file=os.path.join(os.path.abspath("media/just_test/"),name),mode="wb") as output:
        for chunk in b.chunks(): #如果超过2.5M则分块,分为64Kb的块依次读取
            				    #chunks是django.core.files.base.File中的方法
            output.write(chunk)
    return HttpResponse("ok")

分页

数据很多时,无法在一张页面中显示所有数据,所以需要分页显示

Page对象

#通过paginator获得Page对象,每页3条数据;获取第一页(从1开始)
page = Paginator(object_list=Goods.objects.all(),per_page=3).page(1) 
#获得页号的序列 [1,2,3,4]
page.paginator.page_range
#共有多少页
page.paginator.num_pages
#是否还有上一页,下一页
page.has_previous()
page.has_next()
#获取下一页,上一页的页号
page.previous_page_number() 获取上一页编号  如果没有下一页则报错
page.next_page_number()  获取下一页编号  用于template中的上一页链接的参数绑定中 如果没有上一页则报错
#获得当前页号
page.number 当前页号,多用于template中对当前页号追加样式
#获得当前页的数据
page.object_list

View-1

当前页数据展示

#此View的访问路径为:"/page/"
def testquerypage(request):
   #要查询第num页(从1开始)
   num = request.GET.get("num")
   #当前页的对象
   page = Paginator(User.objects.all(),3).page(num)
   return render(request,"users.html",{"page":page})
{# 展示所有数据 #}
{% for user in page.object_list %}
        {{ user.id }} -- {{ user.name }} -- {{ user.pic }} <br/>
{% endfor %}

View-2

展示当前页 + 所有页号

{# 展示所有数据 #}
{% for user in page.object_list %}
	{{ user.id }} -- {{ user.name }} -- {{ user.pic }} <br/>
{% endfor %}
{# 输出所有页号 #}
{% for page_num in page.paginator.page_range %}
	<a href="/page/?num={{ page_num }}">{{ page_num }}</a>
{% endfor %}

View-3

上一页 + 下一页

{# 展示所有数据 #}
{% for user in page.object_list %}
	{{ user.id }} -- {{ user.name }} -- {{ user.pic }} <br/>
{% endfor %}
{# 如果有上一页显示 下一页链接 #}
{% if page.has_previous %}
	<a href="/page/?num={{ page.previous_page_number }}">上一页</a>
{% endif %}
{# 显示所有的页号 链接 #}
{% for page_num in page.paginator.page_range %}
	<a href="/page/?num={{ page_num }}">{{ page_num }}</a>
{% endfor %}
{# 如果有下一页显示 下一页链接 #}
{% if page.has_next %}
	<a href="/page/?num={{ page.next_page_number }}">下一页</a>
{% endif %}

###View-4

样式优化

.a{
    width:34px;
    height: 34px;
    border:1px solid #e1e2e3;
    cursor:pointer;
    display: inline-block;
    text-align: center;
    line-height: 34px;
}
.b{
    border:0;
    width:34px;
    height: 34px;
    cursor:pointer;
    display: inline-block;
    text-align: center;
    line-height: 34px;
}
.c{
    height: 34px;
    padding: 0 18px;
    border:1px solid #e1e2e3;
    line-height: 34px;
    display: inline-block;
}
.c:hover{
    border:1px solid #38f;
}
a{
    text-decoration:none;
}

{#显示当前页数据#}
{% for user in page.object_list %}
        {{ user.age }}--{{ user.name }}-- 
        <img src="{% static user.head_pic.url %}" height="30px"/><br/>
{% endfor %}
{# 判断是否显示上一页链接 #}
{% if  page.has_previous %}
        <a href="/page/test/?num={{ page.previous_page_number }}">
    		<span class="c">上一页</span>
    	</a>
{% endif %}
{# 显示所有页号的链接 #}
{% for num in  page.paginator.page_range %}
        <a href="/page/test/?num={{ num }}">
            {% if num == page.number %}
                 <span class="b">{{ num }}</span>
            {% else %}
                 <span class="a">{{ num }}</span>
            {% endif %}
        </a>
{% endfor %}
{# 判断是否显示下一页链接 #}
{% if  page.has_next%}
        <a href="/page/test/?num={{ page.next_page_number }}">
    		<span class="c">下一页</span>
    	</a>
{% endif %}
    
<a href="{% url 'page:list' %}?num={{ page.paginator.num_pages }}">
	<span class="c">末页</span>    
</a>

中间件 - Middleware(了解)

作用:在View之前和之后执行,追加扩展View中的逻辑

常用作,View中冗余功能的抽取,如强制登录

定义中间件

class MyMiddleAware(MiddlewareMixin):
    def __init__(self,get_response):#初始化
        super().__init__(get_response)
        print("init1")
	#view处理请求前执行
    def process_request(self,request):
        print("request:",request)
    #在process_request之后View之前执行
    def process_view(self,request, view_func, view_args, view_kwargs):
        print("view:",request,view_func,view_args,view_kwargs)
	#view执行之后,响应之前执行
    def process_response(self,request,response):
        print("response:",request,response)
        return response #必须返回response
    #如果View中抛出了异常
    def process_exception(self,request,ex):#View中出现异常时执行
        print("exception:",request,ex)

**中间件中常用的两个过程:**process_request , process_response

注册中间件:每当请求时,所有中间件都会执行自己的生命周期


MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    ....
    'middleware115.middlewares.MyMiddleware',#注册自定义中间件,尽量放在最后注册
]

强制登录实例

class MyMiddleAware2(MiddlewareMixin):
    #如果验证成功,则什么一个不用做,否则返回HttpResponse即可响应请求(中断)
    def process_request(self,request):#强制登录判断
        if "login" not in request.path and "register" not in request.path:
            #路径中如果没有"login" 和 "register"  http://ip:port/xx/xxx/x
            print("登录验证")
            session = request.session #获取session
            if session.get("login"): #判断是否有登录的标记
                print("已登录")
            else:
                print("未登录")
                #return render(request,"login.html") #未登录则,跳转登录页面
                return redirect("xx:xx")
        else:
            print("正在登录") #如果路径中"login"则是登录动作本身
    def process_response(self,request,response):
        print("response:",request,response)
        return response #持续返回响应

CSRF(了解)

(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”

django解决办法:

1> 在form中添加{% csrf_token %},且使用post

2> django 的中间件 django.middleware.csrf.CsrfViewMiddleware

django的实现思路:

1>在请求响应时,生成一个随机的token(令牌),存放于form单的隐藏域和cookie中

2>当次form,发送请求时,会携带隐藏域令牌和cookie中的令牌,此时CsrfViewMiddleware 会先于View

​ 执行,去判断两块令牌是否一致,验明正身

Django内置后台管理-定制(了解)

适合小型项目,快速构建自己的后台系统

1.创建管理员账户
   python manage.py createsuperuser
2.注册model,在App的admin.py中
   admin.site.register(Person)
   admin.site.register(Passport)
   admin.site.register(User)
   admin.site.register(Order)
   admin.site.register(Student)
   admin.site.register(Course)
3.确保,在settins.py中安装了admin,其会扫描每个app的admin.py,进而支持后台管理
   INSTALLED_APPS = [
    'django.contrib.admin',
   	...
   ]
4.启动服务器
  python manage.py runserver
5.访问后台管理页面:http://localhost:8000/admin

定制1

操作选项位置

@admin.register(User)#等价于 admin.site.register(User)
class UserAdmin(admin.ModelAdmin):
    #操作选项位置:数据列表页中的Action选项
    actions_on_top = True
    actions_on_bottom = True

###定制2

定制列名

class User(models.Model):
    name = models.CharField(max_length=20,)
    def cName(self):
        return self.name
    cName.short_description ='姓名' #改列名
   	cName.admin_order_field = 'name' #保证改列名后依然可以排序
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    #在所有数据的列表页面,显示那些列,并使列名是中文 +++++
    list_display = ['cName', 'gender','birth']

定制3

修改Model名

class User(models.Model):
    ...
    class Meta:
        verbose_name="用户" #修改Model名
        verbose_name_plural="用户"  #复数Model名:默认为"Users"

定制4

过滤查询栏

@admin.register(User)
class UserAdmin(admin.ModelAdmin):
   ...
   list_display = ['cName', 'gender','birth']
   #为哪些属性定制右侧过滤栏
   list_filter = ['name', 'gender','birth']

定制5

设置分页

@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    ...
    list_per_page = 5 #每页显示多少条,分页展示数据

定制6

设置搜索框

class UserAdmin(admin.ModelAdmin):
    search_fields = ['name'] #可以以那列为条件搜索,会增加搜索框

定制7

数据显示格式

class User(models.Model):
    age = models.SmallIntegerField()
    name = models.CharField(max_length=20)
    birth = models.DateTimeField()
    head_pic = models.ImageField(upload_to="c115")
    def __str__(self): # 使得后台页面中的列表展示页面,显示结果为 str的返回值
        return str(self.age)+" "+self.name+" "+datetime.strftime(self.birth,"%Y-%m-%d")

定制8

可操作的列

class UserAdmin(admin.ModelAdmin):
	fields = [('name', 'pic'),'age'] #增加和修改页显示的列,且name和pic列在一行显示

定制9

列分组显示

@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    #分组显示,组标题分别为 ‘基本信息’ 和 ‘头像’
    fieldsets = (
        ('基本信息',{"fields":[('name','age')]}),
        ('头像',{"fields":["pic"]})
    )

定制10

关联显示

class OrderInline(StackedInline):#TabularInline
    model = Order  #Order是否已经注册无所谓
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    inlines = [
        OrderInline
    ]

Form(了解)

可以快速定制前端的form表单,并提供参数接收,数据验证。功能看似很全面

但实则使用价值不大,因为有众多前端框架,可以更专业的定制前端逻辑,而django如此定制有些越俎代庖,而且会影响前端框架的建设,无法和前端人员或前端技术对接(如:easyUI,bootstrap等)

所以,此章节请自学,了解即可!

class UserForm(forms.ModelForm):
    class Meta:
        model = User2
        #fields=["name","age","gender"]
        exclude=["salary2"]
def aa(request):
	print("goto a template")
	render(request,"xx.html",{"form":UserForm()})
xx.html
<form action = "xxx" method="post">
	{% csrf_token %}
    <table>
    	{{ form }}
        <tr>
        <td colspan="2" align="center"><input type="submit" value="Submit" /></td>
        </tr>
    </table>
</form>
def test3(request):
    form = UserForm(request.POST)
    if form.is_valid():
        print("表单数据合法")
        #{'name': 'zzz', 'age': 18, 'gender': True, 'birth': datetime.date(2018, 11, 12),...}
        data = form.cleaned_data
        print(data)
    return render(...)
作业
1.员工增加+头像+员工列表回显
2.员工列表分页
3.员工增删改+分页状态的保持
4.强登

你可能感兴趣的:(Python)