Py006-01-06django之url控制path

先看问题

1
2
3
4
5
6
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),
]

考虑下这样的两个问题:

第一个问题,函数 year_archive 中year参数是字符串类型的,因此需要先转化为整数类型的变量值,当然year=int(year) 不会有诸如如TypeError或者ValueError的异常。那么有没有一种方法,在url中,使得这一转化步骤可以由Django自动完成?

第二个问题,三个路由中article_id都是同样的正则表达式,但是你需要写三遍,当之后article_id规则改变后,需要同时修改三处代码,那么有没有一种方法,只需修改一处即可?

在Django2.0中,可以使用 path 解决以上的两个问题。

基本使用

1
2
3
4
5
6
7
8
9
复制代码
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),
]

基本规则:

  • 使用尖括号(<>)从url中捕获值。
  • 捕获值中可以包含一个转化器类型(converter type),比如使用 int:name 捕获一个整数变量。若果没有转化器,将匹配任何字符串,当然也包括了 / 字符。
  • 无需添加前导斜杠。

path转换器

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反向引用。

例子

step001随便建一个类

urlConvert.py

1
2
3
4
5
6
class MyConvert:  
regex = '[0-9]{4}'
def to_python(self, value):
return int(value)
def to_url(self, value): # 反向解析
return '%04d' % value

step002使用register_converter 将其注册到URL配置中

1
2
3
4
5
6
7
8
9
10
# register_converter是必须引入的 帮你注册转换器
from django.urls import register_converter, path
from . import urlConvert, views
# 注册转换器
register_converter(urlConvert.MyConvert, 'yyyy')
urlpatterns = [
path('articles/2003/', views.special_case_2003),
path('articles/<yyyy:year>/', views.year_archive),
...
]

Py006-01-05django路由反向解析

登陆需求前置工作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# step001 新建django项目  mydj003
# step002 设置应用为 app001
# mydj003/urls.py

from django.contrib import admin
from django.urls import path
from app001 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('login/',views.login)
]

# app001/view.py
def login(request):
return render(request,'login.html')
# step003 开发登陆需求 localhost:8000/login
# step004 login.html能提交表单 action先写死为 localhost:8000/login

templates/login.html

<form action="http://127.0.0.1:8000/login/" method="post">
用户名 <input type="text" name="user">
密码 <input type="password" name="pwd">
<input type="submit">
</form>

# step005 python3 manage.py runserver 提交表单发现报 403
此时你要修改一下settings.py里的 csrf配置
注释掉 csrf哪一行(这个问题以后解决)
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# step006 此时你就可以正常提交表单了
访问 localhost:8000/login 会返回登陆页面 get请求
提交按钮 也会访问 localhost:8000/login post请求

分支处理get和post请求

app001/views.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django.shortcuts import render,HttpResponse

def login(request):
print(request.method)

if request.method =='GET':
return render(request,'login.html')
else:
print(request.GET)
print(request.POST)
user=request.POST.get('user');
pwd=request.POST.get('pwd');

if user=='aaa' and pwd =='123':
return HttpResponse("login success")
else:
return HttpResponse("login error")

# 在进行访问 localhost:8000/login 然后提交表单——一切正常

反向解析

刚刚的action里是写死的,但是在开发过程中业务是会变的,这样就导致一旦变了改的就很多了

1
2
3
4
5
6
此时你修改路由
# path('login/',views.login) 修改 为 login.html
path('login.html/',views.login)

# 再去提交 表单 发现提交不好使了
此时你必须把login.html里的 form的action修改才能正常访问!!!!

解决问题

  • 路由别名 name
1
2
3
4
5
6
7
8
9
10
11
# 修改urls.py里的路由
urlpatterns = [
path('admin/', admin.site.urls),
path('login/',views.login,name="Login")
]

# 修改login.html里action
<form action="{% url "Login" %}" method="post">


# 注意模版里的 {% url "Login" %} 是在视图函数 return render的时候 解析Login的url

反向解析函数

step001添加路由

app001/urls.py添加如下路由

1
2
3
4
5
6
7
from django.urls import path,re_path
from . import views

urlpatterns = [
re_path(r'^articles/2003/$',views.x001,name="year2003"),
re_path(r'^articles/([0-9]{4})/$',views.x002,name="y_m")
]

step002路由分发

1
2
3
4
5
6
7
8
9
10
11
from django.contrib import admin
from django.urls import path,re_path,include
from app001 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('login/',views.login,name="Login"),

# 分发
re_path(r"^app001/",include('app001.urls'))

]

step003添加视图函数

  • reverse反向解析函数 传递定义的路由别名来获取url
  • 注意点在视图函数里可以反向解析 urls.py里的任意定义别名的url路由
  • 如果urls.py里的路由含有动态参数,反向解析的时候必须传递对应的参数才可以
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from django.urls import reverse

def x001(request):
url = reverse('year2003')
print(url)
# url2 = reverse('y_m')
# 这条url里有分组,r'^articles/([0-9]{4})/$'
# 所以反向解析的时候,要匹配一个参数才可以
# 正确的使用方式 传递args参数 这里是一个元祖
url2 = reverse('y_m',args=(4009,))
return HttpResponse('2003')

def x002(request,year):
return HttpResponse('year%s'%year)

路由命名空间

step000再开一个应用

1
python manage.py startapp app002

step001添加路由

  • app001/urls.py添加如下路由并使用别名index
  • app002/urls.py添加如下路由并使用别名index
  • 此时app002和app001的路由别名是重复的
1
2
3
urlpatterns = [
re_path('index/',view.index,name="index"),
]

step002路由分发

1
2
3
4
5
6
7
8
9
10
11
from django.contrib import admin
from django.urls import path,re_path,include

urlpatterns = [
path('login/',views.login,name="Login"),

# 分发
re_path(r"^app001/",include('app001.urls'))
re_path(r"^app002/",include('app002.urls'))

]

step003反向解析路由

分别在app001和app002的urls.py的视图函数里反向解析url

1
2
3
4
5
from django.urls import reverse

def index(request):
url = reverse('index');
return HttpResponse(url)

step004访问如下url

1
2
3
4
5
6
localhost:8000/app001/index/
响应 /app002/index


localhost:8000/app002/index/
响应 /app002/index

为什么找到的都是一个路由

  • 因为app001和app002的路由index定义的别名相同,分发的时候就会发生覆盖

名称空间来解决不同应用别名相同覆盖的问题

1.主urls.py里

1
2
3
4
5
6
7
8
9
10
# 使用命名空间
urlpatterns = [
# 分发 子应用 路由别名相同时,反向解析路由的时候就会发生覆盖
# re_path(r"^app001/",include('app001.urls'))
# re_path(r"^app002/",include('app002.urls'))

# 分发 使用命名空间来规避 反向解析路由覆盖问题
re_path(r"^app001/",include(('app001.urls',"app001")))
re_path(r"^app002/",include(('app002.urls',"app002")))
]

2.app001/views.py和app002/views.py里

1
2
3
4
5
6
7
8
9
10
# app001
def index(request):
url = reverse("app001:index")
return HttpResponse(url)


# app002
def index(request):
url = reverse("app002:index")
return HttpResponse(url)

此时反向解析就能解析对应的url里了

Py006-01-04django路由简单配置

此逻辑建立在上一篇博客的代码基础上,请自行拷贝

URLconf

URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于客户端发来的某个URL调用哪一段逻辑代码对应执行。

re_path是django1里的一个函数

  • urls.py里 默认引入的是 from django.urls import path
  • django2向前兼容也可以引入re_path
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# step001 修改  urls.py
from django.contrib import admin
from django.urls import path,re_path

from app001 import views

urlpatterns = [
path('admin/', admin.site.urls),
path('timer/', views.timer),

# 路由配置 路径------》视图函数
re_path(r'^articles/2003/$', views.special_case_2003),
# re_path(r'^articles/([0-9]{4})/$', views.year_archive),
# re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
# re_path(r'^articles/([0-9]{4})/([0-9]{2})/([0-9]+)/$', views.article_detail),
]

# step002 在 app001/views.py里定义 special_case_2003函数

# 引入HttpResponse函数
from django.shortcuts import render,HttpResponse
def special_case_2003(request):
return HttpResponse("special_case_2003")


# step003 打开链接 http://localhost:8000/articles/2003/
你会看到 special_case_2003

很明显 r’^articles/2003/$ 是一个正则匹配

1
2
3
4
5
6
7
8
如果你去掉 r'^articles/2003/$" 里的"$"  
-就可以匹配 articles/2003/01/11

如果你去掉 r'^articles/2003/$" 里的"^"
-就可以匹配 aaa/articles/2003/

如果你去掉 r'^articles/2003/$" 里的"^"和"$"
-就可以匹配 aaa/articles/2003/01/22

注意:

  • 若要从URL 中捕获一个值,只需要在它周围放置一对圆括号。
  • 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
  • 每个正则表达式前面的’r’ 是可选的但是建议加上。它告诉Python 这个字符串是“原始的” —— 字符串中任何字符都不应该转义

新需求:刚刚的articles/2003 不一定永远是这样的

1
2
3
4
5
6
7
8
9
你会有
articles/2003
articles/2004
articles/2005
articles/2006
articles/2007
...
articles/2018
这样每个都配置就很恶心了

正则动态匹配路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 路由修改如下
urlpatterns = [
...

# 路由配置 路径------》视图函数
re_path(r'^articles/2003/$', views.special_case_2003), # 实际是调用 special_case_2003(request)
re_path(r'^articles/([0-9]{4})/$', views.year_archive), # 实际是调用 year_archive(request,1999)
]

'''
re_path(r'^articles/([0-9]{4})/$', views.year_archive) 中

([0-9]{4}) 代表——分组(匹配四位数字) 此时就会多传递一个参数
只要有()就代表分组 就会多传递一个参数 形如 year_archive(request,1999)

[0-9]{4} 如果没有"()"代表不分组,这样调用视图函数的时候这个动态的值就不会当作位置参数传递
形如 year_archive(request)
'''

# views.py里 更新对应的视图函数
def year_archive(request,year):
return HttpResponse("year is %s"%year)

# 访问 http://localhost:8000/articles/2021/
得到内容 year is 2021

注意访问 http://localhost:8000/articles/2003/ 

1
2
3
4
5
返回的是 special_case_2003
因为路由里会按规则顺序进行匹配 刚好第一条规则在我们动态匹配年份的前面

re_path(r'^articles/2003/$', views.special_case_2003),
re_path(r'^articles/([0-9]{4})/$', views.year_archive),

匹配两个动态路由 不仅接受年还支持月份

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 在修改 urls.py
urlpatterns = [
path('admin/', admin.site.urls),
path('timer/', views.timer),
re_path(r'^articles/2003/$', views.special_case_2003),
re_path(r'^articles/([0-9]{4})/$', views.year_archive),
re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), # # 实际是调用 month_archive(request,1999,12)
]

# 在views.py 进行视图函数的定义

def month_archive(request,year,month):
return HttpResponse("year is %s ,month is %s"%(year,month))

# 此时访问 http://localhost:8000/articles/2003/02/
返回 year is 2003 ,month is 02


# 注意
# 注意
# 注意
# 此时如果你去掉 re_path(r'^articles/([0-9]{4})/$', views.year_archive), 的 "$" 就会导致月份没法匹配了

有名分组

参考刚才最后一个例子

1
2
3
4
5
6
7
8
9
10
# 在修改 urls.py
urlpatterns = [
...
re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive), # # 实际是调用 month_archive(request,1999,12)
]

# 在views.py 进行视图函数的定义

def month_archive(request,year,month):
return HttpResponse("year is %s ,month is %s"%(year,month))

如果此时你修改month_archive(request,year,month)中的参数 year和month的位置那么 返回的结果就颠倒了

  • 关键字传参
1
2
3
4
5
6
7
修改路由如下

urlpatterns = [
...
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive)
# 实际是调用 month_archive(request,year=1999,month=12)
]

再也不担心参数位置变更导致的问题了

路由分发

现在就一个应用,假如以后多了呢? 这样路由都写在 mydb002/urls.py里就很恶心 了

  • 1.在app001目录里建立 urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 每个应用的url独立
#
from django.contrib import admin
from django.urls import path,re_path

from app001 import views

urlpatterns = [
re_path(r'^articles/2003/$', views.special_case_2003),
re_path(r'^articles/([0-9]{4})/$', views.year_archive),
re_path(r'^articles/([0-9]{4})/([0-9]{2})/$', views.month_archive),
re_path(r'^articles/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.month_archive)
# 实际是调用 month_archive(request,year=1999,month=12)
]
    1. 修改 主(mydj002目录的) urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from django.contrib import admin
# 引入 include函数
from django.urls import path,re_path,include

from app001 import views

urlpatterns = [
path('admin/', admin.site.urls),
path('timer/', views.timer),
# 内容分发
re_path(r"app001/",include("app001.urls"))
]


# 此时你访问就都可以了
http://localhost:8000/app001/articles/2003/
http://localhost:8000/app001/articles/2004/12/

但是带着app001不太好看

修改主 urls.py

1
2
3
4
5
6
7
8
9
10
urlpatterns = [
...
# 内容分发
# re_path(r"app001/",include("app001.urls"))
re_path(r"^",include("app001.urls"))
]

# 访问就正常了
# http://localhost:8000/articles/2003/
# http://localhost:8000/articles/2003/12/

Py006-01-03django静态文件配置

django静态文件处理之引入jquery

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
1.新建django项目   mydj002
2.设置more settings 里 在application name里输入 app001
3.继续实现timer的效果
app001/views.py里
from django.shortcuts import render
# Create your views here.

def timer(request):
import time
ctime = time.time()

return render(request,"timer.html",{"ctime":ctime})

4. mydj002/urls.py里

from django.contrib import admin
from django.urls import path

from app001 import views

urlpatterns = [
path('admin/', admin.site.urls),
path('timer/', views.timer)
]

5. templates里 放置一个jquery-3.1.1.js
timer.html内容如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
h4{color:red;}
</style>
</head>
<body>
<script src="jquery-3.1.1.js"></script>
<h4>当前时间:{{ ctime }}</h4>
<script>
$('h4').on('click',function(){
$(this).css('color','green')
})
</script>
</body>
</html>

6.此时你单独运行 timer.py发现
ctimer没有正常渲染因为脱离了django单独运行
h4点击后确实变绿了

7.但是你 python3 manage.py runserver 访问 localhost:8000/timer发现jquery并没有加载

为什么jquery没有加载

1
2
因为<script src="jquery-3.1.1.js"></script>里的路径不对
你需要对静态文件进行路径配置才可以访问

配置静态文件路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
1. 在项目里mydj002下新建 statics目录 把jquery-3.1.1.js移到这里
此时你去浏览器里访问 localhost:8000/statics/jquery-3.1.1.js 还是报错
因为你缺少一个配置
因为你缺少一个配置
因为你缺少一个配置

2. 在 mydj002/settings.py里
STATIC_URL = '/static/'
# 添加如下内容
# 注意必须叫 STATICFILES_DIRS这是规定好的
# statics
STATICFILES_DIRS = [
os.path.join(BASE_DIR,"statics")
]

# STATIC_URL 是静态资源目录的一个别名 你访问时候就是 localhost:8000/static/静态文件
# os.path.join(BASE_DIR,"statics") 中的 statics是你刚刚新建的statics目录名

3. localhost:8000/static/jquery-3.1.1.js 发现找到了


4. 在 timer.html里修改 jquery的路径 注意别名
settings.py 里的 别名STATIC_URL = '/static/'
记住你刚刚配置了别名 static
记住你刚刚配置了别名 static
记住你刚刚配置了别名 static
<script src="/static/jquery-3.1.1.js"></script>

5. 访问 http://localhost:8000/timer/ 点击h4发现成功了!!!

Py006-01-02django的下载和基本命令

下载Django

1
2
3
4
pip install django

# 指定版本 我的例子都是基于2.x.x版本以上的
pip install django==2.0.1

查看django下载到哪里去了?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# step1 命令行运行
python
# step2
import sys
# step3
sys.path
# step4 把python的路径复制
# step5 退出 python命令行环境 exit
# step6
cd 你复制的路径
# step7 查看路径下的文件
ls
# step8 打开你安装的python版本的文件
open 3.6
# step9 点开bin文件 看看是否有django-admin文件

创建一个django project

1
2
3
4
5
6
7
8
9
10
# 假设你现在的命令行路径是桌面
django-admin.py startproject mysite
# 这样你就创建了一个如下目录的 mysite工程
mysite
|---manage.py
|---mysite
|---__init__.py
|---settings.py
|---urls.py
|---wsgi.py
  • manage.py —– Django项目里面的工具,通过它可以调用django shell和数据库等。
  • settings.py —- 包含了项目的默认设置,包括数据库信息,调试标志以及其他一些工作的变量。
  • urls.py —– 负责把URL模式映射到应用程序。

在mysite目录下创建应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 切换到mysite目录
cd mysite
# 创建一个新的应用
python manage.py startapp blog

# 你会发现生成了一个blog目录

blog
|---__init__.py
|---admin.py
|---apps.py
|---migrations
|---__init__.py
|---models.py
|---tests.py
|---views.py

此阶段我们只需要关注两个文件

  • models.py 跟数据库打交道
  • views.py 跟视图函数打交道

启动项目

1
2
3
python3 manage.py runserver
# 或者
python3 manage.py runserver 8888

打开浏览器输入[localhost:8888]你就看到小火箭了~~~

通过pycharm创建项目

1
2
3
4
5
6
7
1.直接新建项目选 django:输入 mydj001

2. 点击更多设置 more settings

3. application name里输入一个应用名称如: app001

4. 点击create

完成一个 localhost:8888/timer 的页面现实当前时间戳

第一步:按照路由规则写一个timer路由

1
2
3
4
5
6
7
8
9
10
11
# 修改urls.py
from django.contrib import admin
from django.urls import path

def timer(request):
return

urlpatterns = [
path('admin/', admin.site.urls),
path('timer/', timer)
]

第二步 但是这样显然很耦合——修改如下

1
2
3
4
5
6
7
8
9
10
# 将timer函数定义在 app001/views.py里
from django.contrib import admin
from django.urls import path

from app001 import views

urlpatterns = [
path('admin/', admin.site.urls),
path('timer/', views.timer)
]

第三步

1
2
3
4
5
6
7
8
9
10
11
12
在templates目录新建timer.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h4>当前时间:</h4>
</body>
</html>

第四步 app001/views.py

1
2
3
4
5
6
7
8
9
from django.shortcuts import render

# Create your views here.

def timer(request):
import time
ctime = time.time()
# django会自动去template目录里找timer.htmk
return render(request,"timer.html")

第五步 运行 python3 manage.py runserver

1
2
3
4
5
6
7
8
9
10
11
默认是 localhost:8000你访问就会报错

因为路由里只匹配了两个
urlpatterns = [
path('admin/', admin.site.urls),
path('timer/', views.timer)
]

你应该访问 localhost:8000/timer

# 但是没有具体的值

第六步 处理数据传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 修改app001/views.py
def timer(request):
import time
ctime = time.time()

return render(request,"timer.html",{"ctime":ctime})


# 修改templates/timer.html
<h4>当前时间:{{ ctime }}</h4>


# 在pycharm里修改django项目不用重新启动就能更新
打开 localhost:8000/timer 目标完成!!!

Py006-01-01django初识

django简介

MVC

Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,他们之间以一种插件式的、松耦合的方式连接在一起,模型负责业务对象与数据库的映射(ORM),视图负责与用户的交互(页面),控制器接受用户的输入调用模型和视图完成用户的请求

MTV

Django的MTV模式本质上和MVC是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同,Django的MTV分别是值:

  • M 代表模型(Model): 负责业务对象和数据库的关系映射(ORM)。
  • T 代表模板 (Template):负责如何把页面展示给用户(html)。
  • V 代表视图(View): 负责业务逻辑,并在适当时候调用Model和Template。

除了以上三层之外,还需要一个URL分发器,它的作用是将一个个URL的页面请求分发给不同的View处理,View再调用相应的Model和Template,MTV的响应模式如下所示:

一般是用户通过浏览器向我们的服务器发起一个请求(request),这个请求回去访问视图函数,(如果不涉及到数据调用,那么这个时候视图函数返回一个模板也就是一个网页给用户),视图函数调用模型,模型去数据库查找数据,然后逐级返回,视图函数把返回的数据填充到模板中空格中,最后返回网页给用户。

Py004-03-02pymysql增删改查

CRUD操作

  • 要使用execute来防止sql注入
  • 增删改 必须commit才能生效
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import pymysql

# 建立链接
conn=pymysql.connect(
host='localhost',
port=3306,
user='root',
password='123456',
db='mydb2',
charset='utf8'
)


# 拿到游标
cursor=conn.cursor()

# 执行sql语句
# 增删查
sql='insert into user values(null,%s,%s)'

# 插入一条
# rows=cursor.execute(sql,('bbb','123'))
# print(rows) # 打印 1

# 插入多条
# rows=cursor.executemany(sql,[('axx','333'),('bxx','444')])
# print(rows) # 打印 2

conn.commit()
# 关闭
cursor.close()
conn.close()

查询

  • fetchone查一条默认返回以元组的形式
  • cursor = conn.cursor(pymysql.cursors.DictCursor) 这样返回字典格式的数据
  • fetchmany(n) n代表取的条数
  • fetchall() 取所有
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import pymysql

# 建立链接
conn=pymysql.connect(
host='localhost',
port=3306,
user='root',
password='123456',
db='mydb2',
charset='utf8'
)


# 拿到游标 默认返回元祖的格式
cursor=conn.cursor()
# 返回字典的格式
# cursor = conn.cursor(pymysql.cursors.DictCursor)

# 查询
sql='select * from user;'
rows=cursor.execute(sql)
# 取得结果
res = cursor.fetchone()
res2 = cursor.fetchone()
res3 = cursor.fetchone()
print(res)
print(res2)
print(res3)

conn.commit()
# 关闭
cursor.close()
conn.close()

光标位置

  • scroll(index,mode=’absolute’)
  • scroll(index,mode=’relative’)
1
2
cursor.scroll(3,mode='absolute') # 相对绝对位置移动 从头数3个取
print(cursor.fetchone())
1
2
3
print(cursor.fetchone())
cursor.scroll(2,mode='relative') # 相对相对位置移动 从上一条取出记录数2个取
print(cursor.fetchone())

获取插入之前数据的id

  • cursor.lastrowid
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import pymysql

# 建立链接
conn=pymysql.connect(
host='localhost',
port=3306,
user='root',
password='123456',
db='mydb2',
charset='utf8'
)


# 拿到游标
cursor=conn.cursor()

# 执行sql语句
# 增删查
sql='insert into user values(null,%s,%s)'

# 插入多条
rows=cursor.executemany(sql,[('axx','333'),('bxx','444')])
print(cursor.lastrowid) # 打印插入之前id的值

conn.commit()
# 关闭
cursor.close()
conn.close()

Py004-03-01pymysql模块基本使用

pymysql

1
2
安装python连接数据库的模块pymysql
#pip3 install pymysql

初始工作

建库建表建数据

1
2
3
4
5
6
7
8
9
create database mydb1

create table user (
id int primary key auto_increment,
user varchar(10) not null,
pwd varchar(10) not null
);

insert into user values(null,'aaa','123'),(null,'bbb','111');

pymysql使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import pymysql

user=input('user>>: ').strip()
pwd=input('password>>: ').strip()

# 建立链接
conn=pymysql.connect(
host='localhost',
port=3306,
user='root',
password='123456',
db='mydb2',
charset='utf8'
)


# 拿到游标
cursor=conn.cursor()

# 执行sql语句

sql='select * from user where user = "%s" and pwd="%s"' %(user,pwd)
print(sql)
rows=cursor.execute(sql)

cursor.close()
conn.close()

# 进行判断
if rows:
print('登录成功')
else:
print('登录失败')

sql注入

上述程序执行时你这样输入

你发现没输入密码也成功了,这就是sql注入

1
2
3
4
user>>: aaa" -- xxxx
password>>:回车
select * from user where user = "aaa" -- xxxx" and pwd=""
登录成功

sql注入的几种作案方式

1
2
3
4
5
6
7
8
9
10
11
12
select * from user where user = "aaa" -- xxxx" and pwd=""
sql语句中"--" 代表注释
"-- "之后就没有意义了
实际运行的是 select * from user where user = "aaa"

---------------------------------------------
user>>: xxx" or 1=1 -- hahaha
password>>:回车
select * from user where user = "xxx" or 1=1 -- hahaha" and pwd=""
登录成功

or 1=1 代表永远成立

解决sql注入

  • cursor.execute来防止注入
  • execute来帮你拼接参数条件
1
2
3
4
5
6
7
# sql='select * from user where user = "%s" and pwd="%s"' %(user,pwd)
# print(sql)
# rows=cursor.execute(sql)

sql='select * from user where user = %s and pwd=%s'
print(sql)
rows=cursor.execute(sql,(user,pwd))

Py004-01-20同步和异步的差别

线程池的同步异步

提交任务的两种方式

同步调用

提交完任务后,就在原地等待任务执行完毕,拿到结果,再执行下一行代码,导致程序是串行执行

1
2
3
4
5
6
生活实例:老板叫你去买苹果
前提——你的老板很耿直偏要看着你买回来
1-你没买到之前他就一直wait
2-你买到了苹果
3-你回来了
4-他拿着苹果啃了一口(还是苹果好吃啊!!!)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from concurrent.futures import ThreadPoolExecutor
import time
import random

def la(name):
print('%s is laing' %name)
time.sleep(random.randint(3,5))
res=random.randint(7,13)*'#'
return {'name':name,'res':res}

def weigh(shit):
name=shit['name']
size=len(shit['res'])
print('%s 拉了 《%s》kg' %(name,size))


if __name__ == '__main__':
pool=ThreadPoolExecutor(13)

shit1=pool.submit(la,'alex').result()
weigh(shit1)

shit2=pool.submit(la,'wupeiqi').result()
weigh(shit2)

shit3=pool.submit(la,'yuanhao').result()
weigh(shit3)

异步调用

提交完任务后,不等待任务执行完毕

1
2
3
4
5
6
生活实例:老板叫你去买苹果,并告诉你,你搞定了告诉他一声
前提——你的老板很耿直偏要看着你买回来
1-你没买到之前他就去忙其他事了
2-你买到了苹果,他在王者荣耀
3-你回来了,他在吃鸡
4-你通知他苹果买好了,他咬了口苹果又去吃鸡了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from concurrent.futures import ThreadPoolExecutor
import time
import random

def la(name):
print('%s is laing' %name)
time.sleep(random.randint(3,5))
res=random.randint(7,13)*'#'
return {'name':name,'res':res}


def weigh(shit):
shit=shit.result()
name=shit['name']
size=len(shit['res'])
print('%s 拉了 《%s》kg' %(name,size))


if __name__ == '__main__':
pool=ThreadPoolExecutor(13)

pool.submit(la,'alex').add_done_callback(weigh)

pool.submit(la,'wupeiqi').add_done_callback(weigh)

pool.submit(la,'yuanhao').add_done_callback(weigh)

Py004-01-19进程池和线程池

进程池与线程池

在刚开始学多进程或多线程时,我们迫不及待地基于多进程或多线程实现并发的套接字通信,然而这种实现方式的致命缺陷是:服务的开启的进程数或线程数都会随着并发的客户端数目地增多而增多,这会对服务端主机带来巨大的压力,甚至于不堪重负而瘫痪,于是我们必须对服务端开启的进程数或线程数加以控制,让机器在一个自己可以承受的范围内运行,这就是进程池或线程池的用途,例如进程池,就是用来存放进程的池子,本质还是基于多进程,只不过是对开启进程的数目加上了限制

介绍

1
2
3
4
5
6
官网:https://docs.python.org/dev/library/concurrent.futures.html

concurrent.futures模块提供了高度封装的异步调用接口
ThreadPoolExecutor:线程池,提供异步调用
ProcessPoolExecutor: 进程池,提供异步调用
Both implement the same interface, which is defined by the abstract Executor class.

基本方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1、submit(fn, *args, **kwargs)
异步提交任务

2、map(func, *iterables, timeout=None, chunksize=1)
取代for循环submit的操作

3、shutdown(wait=True)
相当于进程池的pool.close()+pool.join()操作
wait=True,等待池内所有任务执行完毕回收完资源后才继续
wait=False,立即返回,并不会等待池内的任务执行完毕
但不管wait参数为何值,整个程序都会等到所有任务执行完毕
submit和map必须在shutdown之前

4、result(timeout=None)
取得结果

5、add_done_callback(fn)
回调函数

进程池

介绍

1
2
3
4
The ProcessPoolExecutor class is an Executor subclass that uses a pool of processes to execute calls asynchronously. ProcessPoolExecutor uses the multiprocessing module, which allows it to side-step the Global Interpreter Lock but also means that only picklable objects can be executed and returned.

class concurrent.futures.ProcessPoolExecutor(max_workers=None, mp_context=None)
An Executor subclass that executes calls asynchronously using a pool of at most max_workers processes. If max_workers is None or not given, it will default to the number of processors on the machine. If max_workers is lower or equal to 0, then a ValueError will be raised.

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

import os,time,random
def task(n):
print('%s is runing' %os.getpid())
time.sleep(random.randint(1,3))
return n**2

if __name__ == '__main__':
# 设置开启进程的最大数量
executor=ProcessPoolExecutor(max_workers=3)

futures=[]
# 虽然循环里会开10个进程,但是进程池里最多3 其他的进程就会等着——从始至终池子里最多有三个进程
for i in range(10):
# 异步提交任务(只触发任务开启,不等待结果)
future=executor.submit(task,i)
futures.append(future)
# 主进程等待所有子进程的任务都完毕在结束——join操作
# 这时要设置 shutdown(True) or shutdown(wait=True)
executor.shutdown(True)
print('+++>')
for future in futures:
print(future.result())

线程池

介绍

1
2
3
4
5
6
7
ThreadPoolExecutor is an Executor subclass that uses a pool of threads to execute calls asynchronously.
class concurrent.futures.ThreadPoolExecutor(max_workers=None, thread_name_prefix='')
An Executor subclass that uses a pool of at most max_workers threads to execute calls asynchronously.

Changed in version 3.5: If max_workers is None or not given, it will default to the number of processors on the machine, multiplied by 5, assuming that ThreadPoolExecutor is often used to overlap I/O instead of CPU work and the number of workers should be higher than the number of workers for ProcessPoolExecutor.

New in version 3.6: The thread_name_prefix argument was added to allow users to control the threading.Thread names for worker threads created by the pool for easier debugging.

用法

1
把ProcessPoolExecutor换成ThreadPoolExecutor,其余用法全部相同

map方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor

import os,time,random
def task(n):
print('%s is runing' %os.getpid())
time.sleep(random.randint(1,3))
return n**2

if __name__ == '__main__':

executor=ThreadPoolExecutor(max_workers=3)

# for i in range(11):
# future=executor.submit(task,i)

gg = executor.map(task,range(1,12)) #map取代了for+submit
# gg是个生成器 里面存放task返回的结果
for res in gg:
print(res)

回调函数

可以为进程池或线程池内的每个进程或线程绑定一个函数,该函数在进程或线程的任务执行完毕后自动触发,并接收任务的返回值当作参数,该函数称为回调函数

  • add_done_callback(callback)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from concurrent.futures import ThreadPoolExecutor
import requests
import time

def get(url):
print('GET %s' %url)
response=requests.get(url)
time.sleep(3)
return {'url':url,'content':response.text}


def parse(res):
res=res.result()
print('%s parse res is %s' %(res['url'],len(res['content'])))


if __name__ == '__main__':
urls=[
'http://www.cnblogs.com/linhaifeng',
'https://www.python.org',
'https://www.openstack.org',
]

pool=ThreadPoolExecutor(2)

for url in urls:
pool.submit(get,url).add_done_callback(parse)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
from multiprocessing import Pool
import requests
import json
import os

def get_page(url):
print('<进程%s> get %s' %(os.getpid(),url))
respone=requests.get(url)
if respone.status_code == 200:
return {'url':url,'text':respone.text}

def parse_page(res):
res=res.result()
print('<进程%s> parse %s' %(os.getpid(),res['url']))
parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
with open('db.txt','a') as f:
f.write(parse_res)


if __name__ == '__main__':
urls=[
'https://www.baidu.com',
'https://www.python.org',
'https://www.openstack.org',
'https://help.github.com/',
'http://www.sina.com.cn/'
]

p=ThreadPoolExecutor(3)
for url in urls:
p.submit(get_page,url).add_done_callback(parse_page) #parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果