socket编程
scocket 协议位于服务端和客户端之间,b/s架构之间的通信步骤如下图所示。我们web开发做的就是socket服务端的时,通过客户端发来的数据,返回给客户端想要的数据。
简单使用
import socket
sk = socket.socket() #创建socket对象
sk.bind(('127.0.0.1', 8000)) #绑定ip端口
sk.listen() #监听
while True:
conn,addr = sk.accept() # while true接受多个连接
data = conn.recv(2048) # 接收数据
print(data)
conn.send(b'ok') # 返回数据
# conn.send(b'HTTP/1.1 200 OK\r\n\r\n<h1>ok</h1>') #这样不管我们访问什么样的路径,都会返回一样的值,所以需要对不同的路径返回不同的值
conn.close() # 断开连接
返回Ok的时候我们在浏览器测试访问:
可以看见服务端返回的数据无效。是因为返回的数据没有遵守http协议,所以我们在返回数据的时候必须要遵守http规范。
http协议
http请求格式:
下面是我们浏览器访问127.0.0.1:8000时打印出来的data的请求数据:
GET / HTTP/1.1\r\n
Host: 127.0.0.1:8000\r\n
Connection: keep-alive
\r\nsec-ch-ua: " Not A;Brand";v="99", "Chromium";v="96", "Google Chrome";v="96"\r\n
sec-ch-ua-mobile: ?0\r\nsec-ch-ua-platform: "Windows"\r\n
Upgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nSec-Fetch-Site: none\r\n
Sec-Fetch-Mode: navigate\r\n
Sec-Fetch-User: ?1\r\n
Sec-Fetch-Dest: document\r\n
Accept-Encoding: gzip, deflate, br\r\n
Accept-Language: zh-CN,zh;q=0.9\r\n
\r\n
' //这里应该是请求数据的地方,但是由于是get请求,没有数据,所以为空,post的庆祝这里就有
http响应格式:
所以我们前面至恢复一个ok是不行的,没有遵循响应的格式。
响应头部的内容可以不加。我们可以回复这样一个东西。
第一个\r\n是状态行的,第二个是响应正文前面的,我们的正文内容是ok
conn.send(b'HTTP/1.1 200 OK\r\n\r\n<h1>ok</h1>')
但是此时无论我们后面跟的路径是什么,返回的数据都是一样的,所以我们需要根据不同的路径返回不同的内容。
此时的服务端可以看见我们访问的路径:
那我们根据这个路径就可以返回不同的内容了:
根据不同的路径返回不同的内容
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8000))
sk.listen()
/// 简单版本
while True:
conn,addr = sk.accept()
data = conn.recv(2048).decode('utf-8') #将byte类型转化为utf-8
# print(data.split()[1])
url = data.split()[1] #这样可以打印出访问的路径,没有根路径则显示 /
print(url)
conn.send(b'HTTP/1.1 200 OK\r\n\r\n') #这样不管我们访问什么样的路径,都会返回一样的值,所以需要对不同的路径返回不同的值
if url == '/rihan':
conn.send(b'/rihan/')
elif url == '/oumei':
conn.send(b'/oumei/')
else:
conn.send(b'404 not found')
conn.close()
//升级函数版
def rihan(url):
return '欢迎访问日韩板块 {}'.format(url)
def oumei(url):
return '欢迎访问欧美模块 {}'.format(url)
while True:
conn,addr = sk.accept()
data = conn.recv(2048).decode('utf-8') #将byte类型转化为utf-8
# print(data.split()[1])
url = data.split()[1]
print(url)
conn.send(b'HTTP/1.1 200 OK\r\ncontent-type: text/html; charset=utf-8\r\n\r\n')
# 注意这里加上了content-type: text/html; charset=utf-8\r\n,不然会乱码
if url == '/rihan':
ret = rihan(url)
elif url == '/oumei':
ret = oumei(url)
else:
ret = '404 not found'
conn.send(ret.encode('utf-8'))
conn.close()
当要添加的路径过多时,一直去加函数和elif是不合适的,所以多返回数据这一块还得优化:
def rihan(url):
return '欢迎访问日韩板块 {}'.format(url)
def oumei(url):
return '欢迎访问欧美模块 {}'.format(url)
def guochan(url):
return '欢迎访问国产模块 {}'.format(url)
list1 = [
('/rihan',rihan),
('/oumei',oumei),
('/guochan',guochan)
]
while True:
conn,addr = sk.accept()
data = conn.recv(2048).decode('utf-8') #将byte类型转化为utf-8
# print(data.split()[1])
url = data.split()[1]
print(url)
conn.send(b'HTTP/1.1 200 OK\r\ncontent-type: text/html; charset=utf-8\r\n\r\n') #这样不管我们访问什么样的路径,都会返回一样的值,所以需要对不同的路径返回不同的值
func = None
for i in list1:
if url == i[0]:
func = i[1]
break
if func:
ret = func(url) /用这种方式进行自定匹配路径
else:
ret = '404 not found'
conn.send(ret.encode('utf-8'))
conn.close()
我们就只需要修改函数和list就行了,while true 里面的内容不用动
返回html页面:
def home(url):
with open('home.html','r',encoding='utf-8') as f:
ret = f.read()
return ret
home.html内容:<h1>欢迎回家!!!</h1>
返回动态页面
import time
### 返回动态页面 从数据库中拿出需要的数据,不是写死的,可以更换数据内容
def timer(url):
now = time.time()
with open('timer.html','r',encoding='utf-8') as f:
ret = f.read()
return ret.replace('@@time@@',str(now)) /动态生成时间,用now()函数替换timer.html中的@@time@@
list1 = [
('/rihan',rihan),
('/oumei',oumei),
('/guochan',guochan),
('/home',home),
('/time',timer),
]
time.html内容:<h1>现在的时间是:@@time@@</h1>
可以看见每次刷新时间都是会变的。是动态的,而不是议程不变的。
bootstrap 和jQuery前端工具包
web框架的原理
https://www.cnblogs.com/maple-shaw/p/8862330.html
服务器程序和应用程序
对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。
服务器程序负责对socket服务端进行封装,并在请求到来时,对请求的各种数据进行整理。
应用程序则负责具体的逻辑处理,根据不同的路径怎样返回内容。
为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等
。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。
WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。
常用的WSGI服务器有uWSGI、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。
wsgi
web框架的四个功能
web框架的4个功能
1.收发消息 wsgi实现
2.根据不同的路径返回不同的内容
3.返回动态的页面,(从数据库取数据)
4.模板渲染,(字符串替换)
常用框架:
- Django(实现234)
- Flask(2)
- tornado(2)
1功能统一都由wsgi实现
Django下载及使用/创建
在Pycharm中安装或者使用pip安装
创建Django
D:>django-admin startproject aom
在D盘下就会出现一个aom目录
pycharm需要专业版的才可已创建django项目
aom/
├── manage.py # 管理文件
├── templates # 存放模板文件(html文件)
└── aom # 项目目录
├── __init__.py
├── settings.py # 配置
├── urls.py # 路由 --> URL和函数的对应关系
└── wsgi.py # runserver命令就使用wsgiref模块做简单的web server
启动项目 python manage.py runserver 127.0.0.1:8000 默认启动127.0.0.1:8000
更改端口 python manage.py runserver 80
更改ip python manage.py runserver 0.0.0.0:80
Django提供admin后台,便于统一管理用户、权限和权限组,超级用户初始化方法
初始化命令行:
python manage.py createsuperuser
登录功能的实现
这里我们使用bootstrap的简单的登录页面
这是这个页面的html页面
要注意form表单这里:
- 需要有action(表单提交地址) 默认为当前地址,可以不写;
- 需要有method提交方法默认为get,设置为post则提交表单时为post请求
- input标签需要添加name属性,有的标签还需要有value值
- 提交时需要有一个button按钮或者type = submit的input标签
- novalidate不去校验我们要提交的内容的合法性,不如输入邮箱时没有@报错
这样用户才能去提交请求。
然后我们
刷新地址栏,使用的get请求。
刷新这个登录框则使用的是form表单的post请求。
所以我们要区分这两种情况,当刷新页面是get,输入账号密码时是post:
这些信息其实都封装到了request中:
代码如下:
访问输入正确用户密码:
输入错误的信息:
就又返回到了登录页面.
我们不推荐在form表单使用GET的方法,因为用户的账户和密码就会显示在url地址上。
request总结:
request.method 请求方法
request.POST form表单POST请求提交的数据
request.GRT URL上的窗体参数(查询参数)
但是我们登录成功后通常会跳转,就有了 redirect模块
from django.shortcuts import HttpResponse,render,redirect
跳转到我们的index.html所在的路径 /index/;
就会直接跳转到我们的index页面,内容和上面的路径都发生变化,如果直接render返回而不用跳转的话,就只有内容改变,上面的路径不会变。
路径就和内容不符了。
创建一个app
创建app是为了更好的去划分一个django项目,有利于开发的管理和规范。
python manage.py startapp app01
admin是管理后台
apps是我们的app相关的
models是和数据库相关的orm
tests是测试相关
views里面写我们的一些函数
我们如何在settings.py文件中 中注册我们的app:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 'app01', #注册app,直接写名称
'app01.apps.App01Config', #推荐写法,通过路径注册
]
我们现在就可以吧url.py文件中之前写的那些函数放到views中了,放过去之后我们的usrl文件这样写。
orm
上面我们判断用户和密码是写死的,这样不友好,我们可以吧他放到数据库中去取,来让多个账户使用,连接到数据库近行校验。但是我们不直接取写数据库,而是使用一种新的方式orm操作关系型数据库。
ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
ORM在业务逻辑层和数据库层之间充当了桥梁的作用。
面向对象编程把所有实体看成对象(object),关系型数据库则是采用实体之间的关系(relation)连接数据。很早就有人提出,关系也可以用对象表达,这样的话,就能使用面向对象编程,来操作关系型数据库。
简单说,ORM 就是通过实例对象的语法,完成关系型数据库的操作的技术,是"对象-关系映射"(Object/Relational Mapping) 的缩写。
ORM 把数据库映射成对象。
- 数据库的表(table) --> 类(class)
- 记录(record,行数据)–> 对象(object)
- 字段(field)–> 对象的属性(attribute)
settings中已经定义了数据库
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
默认是sqlite3数据库,我们暂时先使用这个,后面用到mysql再说。
如何去modles.py中定义数据表:
class User(models.Model): #创建user表
username = models.CharField(max_length=32) #username字段,相当于varchar32
password = models.CharField(max_length=32)
在Database插件中查看,在plugins中安装 Database-nevagita插件。
我们可以看到目前库中还没有这张表
Termianl执行:
python manage.py makemigrations //检测models文件有无变更,制作成变更文件,当我们注释掉上面的User类在执行的话就会删除这张表
Migrations for 'app01':
app01\migrations\0001_initial.py
- Create model User
如果无变化就会:
就创建了这个迁移文件。这一部分如果出错就删除这个迁移文件,在重新执行上面的命令。
然后在执行
D:\aom>python manage.py migrate /将变更的记录同步到数据库
Operations to perform:
Apply all migrations: admin, app01, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying app01.0001_initial... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying sessions.0001_initial... OK
创建很多表,我们的表已项目名_表名命名。邮件编辑表添加行。
切记要这样添加,不能通过直接点击空白添加。
我们怎样使用哪,有固定的写法:
(<QuerySet [<User: User object>, <User: User object>]>, <class 'django.db.models.query.QuerySet'>)
(<User: User object>, u'alex', u'123', <type 'unicode'>)
(<User: User object>, u'peiqi', u'123', <type 'unicode'>)
//获取一条数据,就返回一个对象
ret = models.User.objects.get(username='alex',password='123')
print(ret,ret.username,ret.password)
# (< User: User object >, u'alex', u'123')
# ret = models.User.objects.get(username='alex',password='1234')
# ret = models.User.objects.get(password='123') #输入错误或者,返回多条数据是不行的
//过滤满足条件的数据
ret = models.User.objects.filter(password='123')
# print(ret,ret.username,ret.password)
print(ret,type(ret))
那我们的login这个函数就可以这样操作了。
def login(request): #优化方法
if request.method == 'POST':
user = request.POST.get('user')
pwd = request.POST.get('pwd')
if models.User.objects.filter(username=user,password=pwd):
return render(request, 'index.html')
return render(request, 'login.html')
总结:
到目前我们在settings中设置了哪些内容:
MIDDLEWARE = [ # 中间件
# 'django.middleware.csrf.CsrfViewMiddleware',
STATIC_URL = '/static/' # 静态文件的别名
# 指定静态文件目录
STATICFILES_DIRS = [
os.path.join(BASE_DIR,'static'),
os.path.join(BASE_DIR,'static1'),
os.path.join(BASE_DIR,'static2'),
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')], 模板地址
INSTALLED_APPS = [
'django.contrib.admin',
# 'app01', #注册app,直接写名称
'app01.apps.App01Config', app地址
orm操作有哪些:
ret = models.User.objects.all() #是一个对象列表
# print(ret,type(ret)) #(<QuerySet [<User: User object>, <User: User object>]>, <class 'django.db.models.query.QuerySet'>)
for i in ret:
print(i,i.username,i.password,type(i.username))
ret = models.User.objects.get(username='alex',password='123') #获取一条数据,就返回一个对象
# (< User: User object >, u'alex', u'123')
ret = models.User.objects.filter(password='123') #过滤满足条件的数据,返回对象列表
使用mysql数据库
首先更改settings里面的配置,
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'login',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': 'root'
}
}
由于django默认使用MySQLDB连接数据库,但是mysqldb只支持python2,所以我们需要在settings文件或者同级的init文件中加上下面两行代码:
import pymysql
pymysql.install_as_MySQLdb()
然后再重新执行表创建的语句:
python manage.py makemigrations
python manage.py migrate
我们用pycharm的插件去连接的时候发现报错:
The server time zone value is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the ‘serverTimezone’ configuration property) to use a more specifc time zone
是mysql 默认使用美国时区,设置为东八区即可:
set time_zone = '+8:00';
set global time_zone = '+8:00'; #只要这一步即可
SET global character_set_server = utf8 ;
ALTER database bookmanager DEFAULT CHARSET utf8;
select schema_name,default_character_set_name from information_schema.schemata; 查看数据库的字符集
flush privileges;
我们新建一个用户,这是测试用这个用户去登录就可以访问了。
总结
- Django相关命令
下载安装:pip install django==1.11.28
创建Django项目:django-admin startproject 项目名
启动项目(切换到项目根目录): python manager.py runserver
创建app:python manager.py startapp app名
数据库迁移命令:
python manager.py makemigrations
python manager.py migrate
- settings配置
BASE_DIR 项目的根目录
INSTALLED_APPS 注册的APP
MIDDLEWARE 中间件,注释掉csrf中间件,才可以随意提交POST请求
TEMPLATES 模板
DIRS:[os.path.join(BASE_DIR,'templates')]
DATABASE 数据库
STATIC_URL='/static/' 静态文件的别名
STATICFILES_DIRS=[os.path.join(BASE_DIR,'static')]
- django 使用MySQL流程
1.创建一个mysql库
2.配置settings
3.使用pymysql模块连接mysql数据库
import pymysql
pymysql.install_as_MySQLdb
4.在app下的modles.py写:
from django.db import models
# Create your models here.
class Publisher(models.Model):
name = models.CharField(max_length=32)
5.执行数据库迁移命令
- urls.py路径和函数对应关系
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^publisher_list/', views.publisher_list),
url(r'^publisher_add/', views.publisher_add),
url(r'^publisher_del/', views.publisher_del),
url(r'^publisher_edit/', views.publisher_edit),
]
- 函数
from django.shortcuts import render,redirect,HttpResponse
def xxx(request):
request.method //请求方式
request.GET //url上携带的参数 ?k1=v1&k2=v2 {} request.GET['k1']不建议
request.GET.get('k1')
request.POST //POST请求提交数据,input框的name属性
return:
HttpResponse('字符串') 返回字符串
render(request,'模板文件名',{}) 返回html页面
redirect('地址') 重定向
- form表单
1 form表单的属性 action地址 method=‘POST’
2.input标签name属性,有些需要value值
3 需要一个butto按钮或者type='submit'的input
- ORM 对象关系映射
数据库的表(table) --> 类(class)
记录(record,行数据)–> 对象(object)
字段(field)–> 对象的属性(attribute)
from app01 import models
查:
models.Publisher.objects.all().order_by('id') //查询所有数据
models.Publisher.objects.get(name='xx',pk=1) //查询一个数据,对象,没有或者多个报错
models.Publisher.objects.filter(name='xx',pk=1) //查询多个数据 QuerySet 对象列表
增:
models.Publisher.objects.create(name='xx') //Publisher对象
删:
models.Publisher.objects.get(pk=1).delete() //对象删除
models.Publisher.objects.filter(pk=1).delete() //多个对象删除
修改:
pub_obj = models.Publisher.objects.get(pk=1)
pub_obj.name = 'xxx'
pub_obj.save() //提交
- 模板语法
return render(request,'模板文件名',{'k1':'xxx','all_list':all+list}) //返回一个html页面
{{ k1 }}
{% for i in all_list %}
{{ forloop.counter }}
{{ i.name }}
{{ i.pk }}
{% endfor %}
图书管理系统
展示出版社
新建项目bookmanager
更改settings文件:
csrf
加templates路径
更改使用mysql数据库。
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'bookmanager',
'HOST': '127.0.0.1',
'PORT': 3306,
'USER': 'root',
'PASSWORD': '123456'
}
}
我们配置访问:
// urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^publisher_list/', views.publisher_list),
]
//views.py
def publisher_list(request):
# 逻辑,1,从数据库获取出版社信息 2, 返回一个页面
ans_publishers = models.Publisher.objects.all()
# for i in ans_publishers:
# print(i)
# print(i.id)
# print(i.name)
return render(request,'publisher_list.html')
//publisher_list.html
<body>
<table border="1 ">
<thead>
<tr>
<th>序号</th>
<th>id</th>
<th>出版社名</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>1</td>
<td>商务印书馆</td>
</tr>
</tbody>
</table>
</body>
测试访问:
现在的内容是我们手动加进去的。
下面我们用模板去渲染。类似于jinja2,Django有自己的语法,{{xxx}} 代表xxx这个变量,而jinja中用的是@@
def publisher_list(request):
# 逻辑,1,从数据库获取出版社信息 2, 返回一个页面
all_publishers = models.Publisher.objects.all() #也可以通过pymysql连接数据库去返回
return render(request,'publisher_list.html',{'publishers':all_publishers})
# 增加了{'publishers':all_publishers}给publisher_list.html传的参数,publishers对应变量,all_publishers是变量的值
//publisher_list.html
<table border="1 ">
<thead>
<tr>
<th>序号</th>
<th>id</th>
<th>出版社名</th>
</tr>
</thead>
<tbody>
{% for i in publishers %}
<tr>
<!-- 拿到循环的次数-->
<td>{{ forloop.counter}}</td>
<!-- 拿到id-->
<td>{{ i.id }}</td>
<!-- 拿到出版社的值-->
<td>{{ i.name }}</td>
</tr>
{% endfor %} #for循环需要以这个结束
</tbody>
</table>
新增出版社
我们应该新增加一个路径,返回给界面一个表单让我们去post提交。把对应的数据提交到数据库,再返回一个界面。
//urls
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^publisher_list/', views.publisher_list),
url(r'^publisher_add/', views.publisher_add),
]
//views
def publisher_add(request):
# get请求返回一个表单,页面包含from表单。
return render(request,'publisher_add.html')
//html
<form action="" method = "post">
出版社名称:<input type="text" name="pub_name">
<button>提交</button>
</form>
打开页面是GET请求。
提交内容是POST请求。
def publisher_add(request):
#POST请求
if request.method == 'POST':
#1,获取用户提交数据
pub_name = request.POST.get('pub_name')
print(pub_name)
#2,将数据新增到数据库
ret = models.Publisher.objects.create(name=pub_name)
print(ret,type(ret))
#3,返回一个重定向到出版社信息展示
return redirect('/publisher_list/')
# get请求返回一个表单,页面包含from表单。
return render(request,'publisher_add.html')
我们在次新增出版社内容后
就重定向到了list界面。
新增补充
在界面展示这里,我们可以按照id排序:
order_by 排序
def publisher_list(request):
# 逻辑,1,从数据库获取出版社信息 2, 返回一个页面
all_publishers = models.Publisher.objects.all().order_by('id')
all_publishers = models.Publisher.objects.all().order_by('-id') 加-则为倒叙排列
但是当前我们可以提价重复的出版社,不会报错,为了控制,我们应该加上相应的逻辑。
控制重复值
def publisher_add(request):
#POST请求
if request.method == 'POST':
#1,获取用户提交数据
pub_name = request.POST.get('pub_name')
print(pub_name)
if models.Publisher.objects.filter(name=pub_name):
return render(request,'publisher_add.html',{'error':'出版社名已存在'})
#2,将数据新增到数据库
ret = models.Publisher.objects.create(name=pub_name)
print(ret,type(ret))
#3,返回一个重定向到出版社信息展示
return redirect('/publisher_list/')
# get请求返回一个表单,页面包含from表单。
return render(request,'publisher_add.html')
//html页面中添加span标签渲染error
出版社名称:<input type="text" name="pub_name"><span>{{ error }}</span>
当前没有error的值,则替换为字符串。
当我们再次提交新华出版社的时候:
就有提示了。
list界面增加跳转按钮
当前我们需要输入地址才可以切换到add页面,我们可以再list页面添加一个按钮跳转到add页面。
<body>
<!--{{ all_publishers }}-->
<a href="/publisher_add/">新增出版社</a> 在这个位置添加a 链接标签
<table border="1 ">
<thead>
点击就可以跳转到add页面。
控制空值
//urls.py
def publisher_add(request):
#POST请求
if request.method == 'POST':
#1,获取用户提交数据
pub_name = request.POST.get('pub_name')
print(pub_name)
#输入为空
if not pub_name: 新增这两行代码
return render(request, 'publisher_add.html', {'error': '出版社名不能为空'})
删除出版社
从数据库删除出版社,修改 list.html
<th>出版社名</th>
<th>操作</th> 新增这一行
<td>{{ i.name }}</td>
<td> <a href="">删除</a> </td> 新增这一行
我们要做的就是点击删除的时候转到相应的逻辑。
为了点击每一行的删除处理不同的数据。
<td>{{ i.name }}</td>
<td> <a href="/publisher_del/?id={{ i.id }}">删除</a> </td> 改为这样既可
由于/publisher_del/?id={{ i.id }} 是一种get的方式,所以拿到这个id我们也用get的方法。
我们把id换成pk,/publisher_del/?pk={{ i.id }}
//html
<td> <a href="/publisher_del/?pk={{ i.id }}">删除</a> </td>
//urls
url(r'^publisher_del/', views.publisher_del),
//views.py
def publisher_del(request):
#获取要删除的数据库id
pk = request.GET.get('pk') 直接从?后面截取pk的值
print(pk)
#根据id查询到一个对象到数据库删除
models.Publisher.objects.get(pk=pk).delete() #第一个pk是主键的意思,数据库中主键是id,故既可写id,也可写pk,第二个是上面的变量
# 返回重定向到展示出版社页面
return redirect('/publisher_list/')
原来id为2的新华出版社已经删除了。
补充
我们在list.html文件里写的
<td>{{ i.id }}</td>
<td>{{ i.pk }}</td>
<td> <a href="/publisher_del/?pk={{ i.pk }}">删除</a> </td>
id有可能不是主键,所以这里我们建议写pk。
当我们手动输入url地址去删除数据时可能不存在数据而报错。
我们就要去过滤:
def publisher_del(request):
#获取要删除的数据库id
pk = request.GET.get('pk')
print(pk)
#根据id到数据库删除
# models.Publisher.objects.get(pk=pk).delete() #查询到一个对象,删除该对象
models.Publisher.objects.filter(pk=pk).delete() #查询到一个对象列表,删除该对象列表
# 返回重定向到展示出版社页面
return redirect('/publisher_list/')
把get改为filter即可,区别在于:
get对 对象进行删除,不存在则报错
filter对列表删除,不存在不会报错
编辑出版社
//list.html
<td>{{ i.name }}</td>
<td>
<a href="/publisher_del/?pk={{ i.id }}">删除</a>
<a href="">编辑</a> 和删除放到一起
</td>
//urls
url(r'^publisher_edit/', views.publisher_edit),
//views
def publisher_edit(request):
pk = request.GET.get('pk')
pub_obj = models.Publisher.objects.get(pk=pk)
# get 返回一个页面 页面包含form表单 input中含有原始数据
return render(request,'publisher_edit.html',{'pub_obj':pub_obj})
//edit.html
<form action="" method = "post">
#出版社名称:<input type="text" name="pub_name" value="{{ pub_obj }}"><span>{{ error }}</span> #先把要编辑的数据放到input框
出版社名称:<input type="text" name="pub_name" value="{{ pub_obj.name }}"><span>{{ error }}</span> #先把要编辑的数据放到input框
<button>提交</button>
接下来就是用户在这里修改数据然后再提交.
//edit.html
出版社名称:<input type="text" name="pub_name" value="{{ pub_obj.name }}"><span>{{ error }}</span>
<button>提交</button>
//views
def publisher_edit(request):
pk = request.GET.get('pk')
pub_obj = models.Publisher.objects.get(pk=pk)
# get 返回一个页面 页面包含form表单 input中含有原始数据
if request.method == 'GET': 点击编辑时为get请求
return render(request,'publisher_edit.html',{'pub_obj':pub_obj})
else:
# post 修改数据库数据
# 获取用户提交的数据
pub_name = request.POST.get('pub_name') 对应html页面中input标签的pub_name属性
# 修改数据库中的数据
pub_obj.name = pub_name #只是在内存中修改了
pub_obj.save() #将修改的操作提交到数据库
# 返回重定向到展示出版社页面
return redirect('/publisher_list/')
修改为:
返回了修改过后的页面。
添加bootstrap样式
//settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
创建这个目录。
到下面这个地址,右键检查,
https://v3.bootcss.com/examples/dashboard/
只复制body中的内容
保留这两个js
现在访问就可以看到初步的效果。
在添加头部:
<link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/dashboard.css">
</head>
效果如下
外键连接书本和出版社
一个出版社有很多书,要把他们连接起来,就得用到外键,满足一对多,多对一的关系。
# Create your models here.
class Publisher(models.Model):
name = models.CharField(max_length=32)
class Book_list(models.Model):
name = models.CharField(max_length=32)
# 外键指向Publisher类,也可以写'Publisher',不过要写到Publisher类的上面,
publisher = models.ForeignKey(Publisher,on_delete=models.CASCADE,default=1)
"""
on_delete: 2.0版本后必选
models.CASCADE 默认级联删除,删除Publisher表的一行数据,Book表对应的内容也会删除。
models.PROTECT 保护模式,当Publisher表中数据还关联着其他表的时候,就进制删除Publisher表
models.SET(v) 删除后设置为某个值
models.SET_DEFAULT() 删除后设置为默认值,需要加上 default=
models.SET_NULL() 删除后设置为NULL
"""
D:\bookmanager>python manage.py makemigrations
D:\bookmanager>python manage.py migrate
含有外键的表就创建好了。django会自动的给外键的字段添加一个_id结尾。
这样 book.publisher代表外键出版社的对象,book.publisher_id则代表对应的出版社id
//urls.py
url(r'^book_list/', views.book_list),
//views.py
# 展示书籍
def book_list(request):
#查询所有书籍
all_books = models.Book_list.objects.all()
#返回页面
# for book in all_books:
# print(book)
# print(book.pk)
# print(book.name)
# print(book.publisher_id)
# print(book.publisher) #拿到关联的publisher的对象
# print(book.publisher.pk,book.publisher.name)
# return HttpResponse('books')
return render(request, 'book_list.html', {'all_books': all_books})
//html
<table border="1">
<thead>
<tr>
<th>序号</th>
<th>id</th>
<th>书名</th>
<th>出版社</th>
</tr>
</thead>
<tbody>
{% for book in all_books %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ book.pk }}</td>
<td>{{ book.name }}</td>
<td>{{ book.publisher.name }}</td>
</tr>
{% endfor %}
</tbody>
</table>
简单的界面:
新增书籍
//urls.py
url(r'^book_add/', views.book_add),
//views.py
def book_add(request):
error = ''
if request.method == 'POST':
#POST 获取用户提交的数据
book_name = request.POST.get('book_name')
pub_id = request.POST.get('pub_id')
#判断为空
if not book_name:
error = 'bookname not null'
#判断重复
elif models.Book.objects.filter(name=book_name):
error = 'bookname already exists'
# 插入数据至数据库
else:
models.Book.objects.create(name=book_name, publisher_id=pub_id)
return redirect('/book_list/')
#查询所有的出版社
all_publishers = models.Publisher.objects.all()
#GET请求返回一个包含form表单的页面
return render(request, 'book_add.html', {'all_publishers': all_publishers, 'error': error})
//book_add.html
<body>
<form action="" method="post">
<p>
书名: <input type="text" name="book_name"> <span> {{ error }} </span>
</p>
<p>
出版社:
<select name="pub_id" id="">
{% for publisher in all_publishers %}
<option value="{{ publisher.pk }}">{{ publisher.name }}</option> value为name="pub_id"赋值为publisher.name对应的pk
{% endfor %}
</select>
</p>
<button>提交</button>
</form>
</body>
删除书籍
//urls
url(r'^book_del/', views.book_del),
//views
def book_del(request):
#获取用户要删除的id
pk = request.GET.get('id')
#获取要删除的数据,执行删除
models.Book.objects.filter(pk=pk).delete()
#返回一个出版社
return redirect('/book_list/')
//book_list.html
<table border="1">
<thead>
<tr>
<th>序号</th>
<th>id</th>
<th>书名</th>
<th>出版社</th>
<th>操作</th> 新增部分
</tr>
</thead>
<tbody>
{% for book in all_books %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ book.pk }}</td>
<td>{{ book.name }}</td>
<td>{{ book.publisher.name }}</td>
<td>
<a href="/book_del/?id={{ book.pk }}">删除</a> 新增部分
</td>
编辑书籍
GET请求
//urls
url(r'^book_edit/', views.book_edit),
//views
def book_edit(request):
#get请求
#查询要编辑对象的id
pk = request.GET.get('id')
#根据id查到编辑对象
book_obj = models.Book.objects.get(pk=pk)
print(book_obj.name)
#返回页面
all_publishers = models.Publisher.objects.all()
return render(request, 'book_edit.html',{'book_obj': book_obj, 'all_publishers':all_publishers})
//list.html
<a href="/book_del/?id={{ book.pk }}">删除</a>
<a href="/book_edit/?id={{ book.pk }}">编辑</a> 新增
//edit.html
<body>
<form action="" method = "post">
<p>
书名:<input type="text" name="book_name" value="{{ book_obj.name }}"><span>{{ error }}</span>
</p>
<p>
出版社:
<select name="pub_id" id="">
{% for publisher in all_publishers %}
<option value="{{ publisher.pk }}">{{ publisher.name }}</option>
{% endfor %}
</select>
</p>
<button>提交</button>
</form>
但是书名和出本社不对称,这是因为没有给一个selected默认值,去匹配书名。
<form action="" method = "post">
<p>
书名:<input type="text" name="book_name" value="{{ book_obj.name }}"><span>{{ error }}</span>
</p>
<p>
出版社:
<select name="pub_id" id="">
{% for publisher in all_publishers %}
{% if book_obj.publisher == publisher %} 判断publisher对象是否一致
<option selected value="{{ publisher.pk }}">{{ publisher.name }}</option> selected选中,给个默认值,
{% else %}
<option value="{{ publisher.pk }}">{{ publisher.name }}</option> 其他的值不选中
{% endif %}
{% endfor %}
</select>
</p>
<button>提交</button>
这样选中书籍就会带出正确的出版社。
加上POST请求:
def book_edit(request):
#查询要编辑对象的id
pk = request.GET.get('id')
#根据id查到编辑对象
book_obj = models.Book.objects.get(pk=pk)
# post请求
if request.method == 'POST':
# 获取用户新提交的数据
book_name = request.POST.get('book_name')
pub_id = request.POST.get('pub_id')
# 编辑的对象做对应的修改
book_obj.name = book_name
book_obj.publisher_id = pub_id
book_obj.publisher = models.Publisher.objects.get(pk=pub_id)
book_obj.save()
#方式二
models.Book.objects.filter(pk=pk).update(name=book_name,publisher_id=pub_id) #这两种方法都可以
# 重定向
return redirect('/book_list/')
#get请求
#返回页面
all_publishers = models.Publisher.objects.all()
return render(request, 'book_edit.html',{'book_obj': book_obj, 'all_publishers':all_publishers})
当前如果我们要改的字段很多的话,就不好用了:
# 编辑的对象做对应的修改
#方式一
# book_obj.name = book_name
# book_obj.publisher_id = pub_id
# # book_obj.publisher = models.Publisher.objects.get(pk=pub_id)
# book_obj.save()
#方式二
models.Book.objects.filter(pk=pk).update(name=book_name,publisher_id=pub_id) 更简洁
也是可以的
回顾
书籍添加样式
前面我们已经给出版社的展示添加了一个样式,现在我们给书籍也添加相同的样式用于展示。
//booklist.html
<li><a href="/publisher_list/">出版社列表</a></li>
<li class="active"><a href="/book_list/">书籍列表</a></li> class=active的作用是选择哪个,哪个就处于激活状态,加个背景
//publisher_list
<li class="active"><a href="/publisher_list/">出版社列表</a></li>
<li><a href="/book_list/">书籍列表</a></li>
点击出版社列表就跳转到了这里。
现在给新增也添加上样式:
//publisher_list.html
<h2 class="sub-header">出版社列表</h2>
<a class="btn btn-primary btn-sm" href="/publisher_add/">新增</a> 新增这一行
//publisher_add.html
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">Project name</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Dashboard</a></li>
<li><a href="#">Settings</a></li>
<li><a href="#">Profile</a></li>
<li><a href="#">Help</a></li>
</ul>
<form class="navbar-form navbar-right">
<input type="text" class="form-control" placeholder="Search...">
</form>
</div>
</div>
</nav>
<div class="container-fluid">
<div class="row">
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li><a href="/publisher_list/">出版社列表</a></li>
<li class="active"><a href="/book_list/">书籍列表</a></li>
<li><a href="#">Analytics</a></li>
<li><a href="#">Export</a></li>
</ul>
</div>
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<div class="panel panel-primary">
<div class="panel-heading">新增出版社</div>
<div class="panel-body">
<form class="form-horizontal" action="" method = "post">
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">出版社名称</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="pub_name" id="inputEmail3" placeholder="请输入出版社名称">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-default">提交</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js" integrity="sha384-nvAa0+6Qg9clwYCGGPpDQLVpLNn0fRaROjHqs13t4Ggj3Ez50XnGQqc/r8MhnRDZ" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/@bootcss/v3.bootcss.com@1.0.29/dist/js/bootstrap.min.js"></script>
</body>
当前我们直接点击提交页面不会变,因为views里面的逻辑如果输入为空则会报错,并重新返回add页面。
所以:
<div class="form-group {% if error %}has-error{% endif %}"> //当传进来的error存在时则添加has-error样式
<label for="inputEmail3" class="col-sm-2 control-label">出版社名称</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="pub_name" id="inputEmail3" placeholder="请输入出版社名称">
<span class="help-block">{{ error }}</span> //新增一个span标签打印error的内容help-block为提示信息类型
</div>
</div>
<div class="panel-heading">编辑出版社</div> /改这里
<div class="panel-body">
<form class="form-horizontal" action="" method = "post">
<div class="form-group {% if error %}has-error{% endif %}">
<label for="inputEmail3" class="col-sm-2 control-label">出版社名称</label>
<div class="col-sm-8">
<input type="text" class="form-control" name="pub_name" value="{{ pub_obj.name }}" id="inputEmail3" placeholder="请输入出版社名称"> /这里添加value="{{ pub_obj.name }}即可
<span class="help-block">{{ error }}</span>
编辑书籍样式
为book_add添加样式
多对多关系创建
创建作者表。一个作者可以写多个书,多个做个也可以写一本书,这样我们在记录作者表的时候就会有重复的数据,所以:
多对多关系的时候需要创建第三张表,这个表有两个外键分别关联作者表和书籍表。类似下面:
auther_book就可以反映多对多的关系。
但是这个表示不需要创建的,只需要:
class Author(models.Model):
name = models.CharField(max_length=32)
books = models.ManyToManyField('Book') //表示和Book表呈现多对多的关系,也可以写在Book表中指向author表
就会自动的创建这两张表。
这样就代表老王 和aom 写了阿宾正传,大头一个人写了578三本书。
展示作者
url(r'^author_list/', views.author_list),
def author_list(request):
# 查询所有的作者
all_authors = models.Author.objects.all()
for author in all_authors:
print(author)
print(author.id)
print(author.name)
print(author.books, type(author.books)) #是一个关系管理对象,类似于外键对象,可用户获取信息
print(author.books.all()) #所关联的所有对象,是一个对象列表,可以循环
print('*' * 40)
return render(request,'author_list.html',{'all_authors' : all_authors})
Author object
2
aom
app01.Book.None <class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager'>
<QuerySet [<Book: Book object>]>
****************************************
Author object
3
大头
app01.Book.None <class 'django.db.models.fields.related_descriptors.create_forward_many_to_many_manager.<locals>.ManyRelatedManager'>
<QuerySet [<Book: Book object>, <Book: Book object>, <Book: Book object>]>
****************************************
<body>
<table border="1">
<thead>
<tr>
<th>序号</th>
<th>ID</th>
<th>姓名</th>
<th>代表作</th>
</tr>
</thead>
<tbody>
{% for author in all_authors %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ author.pk }}</td>
<td>{{ author.name }}</td>
<td>
{% for book in author.books.all %} 这里不用写all() 也会执行
《 {{ book.name }} 》
{% endfor %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
添加作者
//urls.py
url(r'^author_add/', views.author_add),
//views.py
def author_add(request):
#POST获取用户提交数据,
if request.method == 'POST':
author_name = request.POST.get('author_name')
book_ids = request.POST.getlist('book_ids') #获取多个数据用getlist,get只取得最后一个值
print(request.POST) #<QueryDict: {'author_name': ['纯天然'], 'book_ids': ['1', '2']}>
#插入作者数据库
author_obj = models.Author.objects.create(name=author_name)
#作者和书籍绑定多对多关系
author_obj.books.set(book_ids)
# 然后返回到作者展示页
return redirect('/author_list/')
# get
# 查询所有书籍
all_books = models.Book.objects.all()
# 返回一个页面,包含form表单,让用户输入
return render(request,'author_add.html',{'all_books': all_books})
//html
<form action="" method="POST">
<p>
作者姓名:<input type="text" name="author_name">
</p>
<p>
代表作:
<select name="book_ids" id="" multiple> #多选
{% for book in all_books %}
<option value="{{ book.pk }}">{{ book.name }}</option>
{% endfor %}
</select>
</p>
<button>提交</button>
</form>
删除作者
url(r'^author_del/', views.author_del),
def author_del(request):
# 获取要删除的对象
pk = request.GET.get('id')
# 根据id查到对象进行删除,删除了作者和对应的书籍关系
models.Author.objects.filter(pk=pk).delete()
# 返回作者展示页面
return redirect('/author_list/')
<table border="1">
<thead>
<tr>
<th>序号</th>
<th>ID</th>
<th>姓名</th>
<th>代表作</th>
<th>操作</th> 新增
</tr>
</thead>
<tbody>
{% for author in all_authors %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ author.pk }}</td>
<td>{{ author.name }}</td>
<td>
{% for book in author.books.all %}
《 {{ book.name }} 》
{% endfor %}
</td>
<td>
<a href="/author_del/?id={{ author.pk }}">删除</a>
<a href="/author_edit/?id={{ author.pk }}">编辑</a> //新增,带出作者的id
</td>
</tr>
{% endfor %}
</tbody>
</table>
编辑作者
url(r'^author_edit/', views.author_edit),
//views.py
def author_edit(request):
# 获取编辑对象的id
pk = request.GET.get('id')
# 根据id获取到作者对象
author_obj = models.Author.objects.get(pk=pk)
if request.method == 'POST':
# POST
#获取用户提交的数据
author_name = request.POST.get('author_name')
book_ids = request.POST.getlist('book_ids')
#修改数据
author_obj.name = author_name
author_obj.save()
#修改和书籍的对应关系,重新设置
author_obj.books.set(book_ids)
#重定向
return redirect('/author_list/')
# get
# 获取所有的书籍
all_books = models.Book.objects.all()
# 返回一个页面包含作者的姓名,包含代表作
return render(request, 'author_edit.html', {'author_obj': author_obj,'all_books': all_books})
//html
<form action="" method="POST">
<p>
作者姓名:<input type="text" name="author_name" value="{{ author_obj.name }}">
</p>
<p>
代表作:
<select name="book_ids" id="" multiple>
{% for book in all_books %}
{% if book in author_obj.books.all %}
<option selected value="{{ book.pk }}">{{ book.name }}</option>
{% else %}
<option value="{{ book.pk }}">{{ book.name }}</option>
{% endif %}
{% endfor %}
</select>
</p>
<button>提交</button>
</form>
就编辑成功了。
总结
// ORM操作
from app01 import models
# 查询
pub_obj = models.Publisher.objects.all() /查询所有的数据,Queryset 对象列表
models.Publisher.objects.get(name='xx',pk='1') /获取唯一的一个对象
models.Publisher.objects.filter(name='xx') /获取多个对象,对象列表
pubobj.pk pub_obj.name
book_obj.pub / 外键,即所关联的Publisher对象
book_obj.pub_id / 外键的id,这是django的快捷方式,也可以book_obj.pub.id
author_obj.books / 多对多字段,是一个关系管理对象
author_obj.books.all() /关联的所有对象,对象列表
# 新增
models.Publisher.objects.create(name='xx')
models.Books.objects.create(name='xx',pub='出版社对象')
models.Books.objects.create(name='xx',pub_id='出版社对象id')
author_obj = models.Author.objects.craete(name='xx')
author_obj.books.set(book_ids)
# 删除
models.Publisher.object.get(pk=1).delete() //通过对象删除
models.Publisher.object.filter(pk=1).delete() //批量删除
# 修改
pub_obj.name = 'xx' //内存中修改
pub_obj.save() //提交到数据库
models.Publisher.object.filter(pk=1).update(name='xx')
# 模板
render(request, 'book_add.html', {'all_publishers': all_publishers, 'error': error})
<div class="form-group {% if error %}has-error{% endif %}"> /error存在时则有has-error错误提示
<span class="help-block">{{ error }}</span> //error的使用
{{ for publisher in all_publishers }}
{{ forloop.counter }}
{{ publisher.name }}
{% if 条件1 %}
(% elif 条件2 %)
{% else %}
{% endif %}
{{ endfor }}
其他细节和内容
MVC和MTV
MVC,全名是Model View Controller,是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller),具有耦合性低、重用性高、生命周期成本低等优点。
Django框架的设计模式借鉴了MVC框架的思想,也是分成三部分,来降低各个部分之间的耦合性。
Django框架的不同之处在于它拆分的三部分为:Model(模型)、Template(模板)和View(视图),也就是MTV框架。
- Model(模型):负责业务对象与数据库的对象(ORM)
- Template(模版):负责如何把页面展示给用户
- View(视图):负责业务逻辑,并在适当的时候调用Model和Template
此外,Django还有一个urls分发器,它的作用是将一个个URL的页面请求分发给不同的view处理,view再调用相应的Model和Template
MVC:
M:model 模型,操作数据库
V:view 视图 展示数据
C:controller 控制器 流程 业务逻辑
MTV
M:model ORM
T:template 模板
V:view 视图,业务逻辑
模板
Django模板中只需要记两种特殊符号:
{{ }}和 {% %}
{{ }}表示变量,在模板渲染的时候替换成值,{% %}表示逻辑相关的操作。
简单使用
新建一个项目进行模板的学习。
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^template_test/', views.template_test),
]
def template_test(request):
num = 1
string = 'i love you'
name_list= ['老曹','老李','老王']
dic = {'name':'alex','age':'18'}
tup = ('天','地','人')
name_set = {'天','地','人'}
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def __str__(self):
return "<Person object {} : {}>".format(self.name,self.age)
def talk(self,str):
return '你在无中生友'
alex = Person('alex',23 )
return render(request,
'template_test.html',
{
'num':num,
'string':string,
'name_list':name_list,
'dic':dic,
'tup':tup,
'name_set':name_set,
'alex':alex,
}
)
<body>
{{ num }}
<br>
{{ string }}
<br>
{{ name_list }} {{ name_list.0 }} /0是索引,跟python中的[0]方式不停,而且不能用负数
<br>
{{ dic }} {{ dic.name }} {{ dic.keys }} /用 . 带出属性和方法
<br>
{{ tup }}
<br>
{{ name_set }}
<br>
{{ alex }}
<br>
{{ alex.name }} : {{ alex.talk }}
</body>
我们可以看到使用了,而且.操作只能调用不带参数的方法
.索引 .属性 .key .方法 //方法后不加()
优先级为:
.key > .属性 > .方法 > .索引
过滤器Filters
过滤器,用来修改变量的显示结果。最多有一个参数。
语法: {{ value|filter_name:参数 }}
’:'左右没有空格没有空格没有空格
- default
return render(request,
'template_test.html',
{
'num':num,
'string':string,
'name_list':name_list,
'dic':dic,
'tup':tup,
'name_set':name_set,
'alex':alex,
'kong':False, 空字符串或者空列表,空字典,false
}
)
//html
{{ baobei}} //无这个变量
<br>
{{ baobei | default:'曹仔'}} // |左后可以加空格,但是:左右不可以
<br>
{{ kong|default:'暂无数据' }}
default就可以进行替换
- filter、add
filesizeformat:将值格式化为一个 “人类可读的” 文件尺寸 (例如 ‘13 KB’, ‘4.1 MB’, ‘102 bytes’, 等等
add:类似于python的 + ,可对数字,字符串列表进行相加
'size':1,
'size1':1024,
//html
{{ size|filesizeformat }}
<br>
{{ size1|filesizeformat }}
<br>
{{ 4|add:2 }}
<br>
{{ '4'|add:'-2' }}
<br>
{{ '4'|add:'xxx2' }}
<br>
{{ name_list|add:name_list }}
- lower、upper、title
{{ alex.name|upper }}
{{ alex.name|upper|lower }}
{{ alex.name|title}} 首字母大写
- length
{{ alex.name|length }} 4
<br>
{{ name_list|length }} 3
<br>
{{ name_list|add:name_list|length }} 6
- slice切片
{{ name_list|slice:'::' }} 所有值
<br>
{{ name_list|slice:'0:2'}} 第0,1个值
<br>
{{ name_list|slice:'::2' }} 步长为2,所以取第1,3个值
<br>
{{ name_list|slice:'::-2' }} 步长-2,逆序取第1,3
<br>
{{ name_list|slice:'::3' }} 步长3,去第一个
<br>
{{ name_list|slice:'::-1' }}
<br>
{{ name_list|slice:'-1:-3:-1' }} 第-1到-3,方向为-1
- first、last、join
{{ name_list|first }}
<br>
{{ name_list|last }}
<br>
{{ name_list|join:'//' }}
<br>
- truncatechars、truncatewords 如果字符串字符多于指定的字符数量,那么会被截断。截断的字符串将以可翻译的省略号序列(“…”)
'long_str':'这样就是从右到左 提取元素 那么大家 一定明白了 如果是步长是正数就是从左到右提取元素'
{{ long_str|truncatechars:'9'}} ...也占三个位置
<br>
{{ long_str|truncatewords:'4'}} 按照单词去取
<br>
- date配置
now = datetime.datetime.now()
//html
{{ now|date:"Y-m-d H:i:s"}}
快捷设置:settings.py
USE_L10N = False //设置为False
DATETIME_FORMAT = 'Y-m-h H:i:s'
这样就不用用上面的date过滤器了
- safe
Django的模板中会对HTML标签和JS等语法标签进行自动转义,原因显而易见,这样是为了安全。但是有的时候我们可能不希望这些HTML元素被转义,比如我们做一个内容管理系统,后台添加的文章中是经过修饰的,这些修饰可能是通过一个类似于FCKeditor编辑加注了HTML修饰符的文本,如果自动转义的话显示的就是保护HTML标签的源文件。为了在Django中关闭HTML的自动转义有两种方式,如果是一个单独的变量我们可以通过过滤器“|safe”的方式告诉Django这段代码是安全的不必转义。
<script>
for (var i = 0; i < 5; i++) {
alert('11111')
}
</script>
比如这段代码如果渲染到了我们的页面中就会不断的产生提示的效果。
为了安全Django会对他们进行转义,加上两个"" :
'a':'<a href="http://www.baidu.com">路费</a>',
我们未来正常渲染,就需要safe过滤器
{{ a }}
<br>
{{ a|safe }}
#from django.utils.safestring import mark_safe
自定义过滤器
- 在app下创建一个名为templatetag的python包 固定,名不能变
- 创建一个文件mytags.py,名字随意
- 文件中注册tag library并添加函数+装饰器:
from django import template
register = template.Library() register固定名称,注册过滤器对象
@register.filter
def add_arg(value,arg):
#功能
return "{}_{}".format(value,arg)
//html
{% load mytags %} //导入包
{{ alex.name|add_arg:'dsb' }} //使用自定义过滤器
专业版重启项目,社区版需要在settings文件中:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
'libraries':{
'my_tags': 'app01.templatetags.tags', 加入这两行
}
},
},
]
使用:
{% load my_tags %}
{{ alex.name|add_arg:'dsb' }}
就可以使用了。
逻辑操作
- for
<ul>
{% for name in name_list %}
<li>{{ forloop.last }}-{{ name }}</li> //判断是否是最后一个循环
{% endfor %}
</ul>
其他可用参数:
Variable | Description |
---|---|
forloop.counter | 当前循环的索引值(从1开始) |
forloop.counter0 | 当前循环的索引值(从0开始) |
forloop.revcounter | 当前循环的倒序索引值(到1结束) |
forloop.revcounter0 | 当前循环的倒序索引值(到0结束) |
forloop.first | 当前循环是不是第一次循环(布尔值) |
forloop.last | 当前循环是不是最后一次循环(布尔值) |
forloop.parentloop | 本层循环的外层循环 |
- empty
//views
'kong':'', kong变量为空字符串
//html
{% for i in kong %}
{{ i }}
{% empty %}
空空如也 //当i没有内容的时候返回这个值
{% endfor %}
- if else
注意!:
if语句支持 and 、or、==、>、<、!=、<=、>=、in、not in、is、is not判断,过滤器。
不支持用 算数运算,不支持连续判断
{% if alex.age > 30 %}
old man!
{% elif alex.age < 30 %}
young man!
{% elif alex.age|add:1 == 30 %}
old boy!
{% endif %}
{% if 10 > 5 > 1 %} 和python不同,和js语法相同,10>5为 True,在拿 True 和1进行判断
正确
{% else %}
错误
{% endif %}
- with定义中间变量
<!--{% with a=name_list.0 %}-->
{% with name_list.0 as a %} //两种方式都可以
{{ a }}
{{ a }}
{{ a }}
{% endwith %}
- csrf_token
这个标签用于跨站请求伪造保护。在页面的form表单里面写上{% csrf_token %}
浏览器 访问 服务器 A
A 返回一个表单 ,但是提交地址写的服务器 B
B无法判断表单是否符合自己的规范,不符合则拒绝表单的提交。加上csrf_token就OK 了
//form,html
<form action="" method="POST">
<input type="text" name="k1">
<button>提交</button>
</form>
浏览器访问提交的时候服务器会拒绝这个POST请求,因为它无法判断这个form表单是自己的还是别的服务器的表单。
<form action="" method="POST">
{% csrf_token %} 加上这一行标签
<input type="text" name="k1">
<button>提交</button>
</form>
就可以看到转换成了隐藏的一个csrf标签。这样服务器就认可了。
1.跨站请求伪造攻击简介
跨站请求伪造(Cross-site request forgery)简称为CSRF,这是一种对网站的恶意利用。CSRF实际上就是攻击者通过各种方法伪装成目标用户的身份,欺骗服务器,进行一些非法操作,但是这在服务器看来却是合法的操作。
django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成。而对于django中设置防跨站请求伪造功能有分为全局和局部。
全局:中间件 django.middleware.csrf.CsrfViewMiddleware;
局部:@csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。@csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。
在渲染模板时,django会把 {% csrf_token %} 替换成一个元素。在提交表单的时候,会把这个token给提交上去。
django默认启动 'django.middleware.csrf.CsrfViewMiddleware’中间件, 这个中间件就是来验证csrf_token的。如果没有加csrf_token,就会出错。
补充:
加减操作我们可以通过add过滤器完成,但是乘除可以通过自定过滤器完成,也可以:
1
<span>{% widthratio 100 2 1 %}</span> 100/2*1 50
<span>{% widthratio 100 1 2 %}</span> 100/1*2 200
2
//mytags.py 新增过滤器
from django.utils.safestring import mark_safe
@register.filter()
def show_a(name,url):
return mark_safe('<a href="http://{}">{}</a>'.format(url,name))
//html
{{ '百度'|show_a:'www.baidu.com' }}
mark-safe的作用就相当于safe过滤器{{ ‘百度’|show_a:‘www.baidu.com’|safe }},这里的safe就可以省略了
母版和继承
我们通常会在母板中定义页面专用的CSS块和JS块,方便子页面替换。
当前我们的publisher_list和book_list 分开使用了两个相同html样式,没有使用母版,author_list 还未配置html样式。
现在,我们以publisher_list为母版,应用到其他两个页面中:
//publisher_list.html
{% block main %}
<h2 class="sub-header">出版社列表</h2>
<a class="btn btn-primary btn-sm" href="/publisher_add/">新增</a>
<div class="table-responsive">
<table class="table table-bordered table-hover">
...
</table>
</div>
{% endblock %}
将中间的内筒部分放到{% block main %}块中,相当于
吧出版社列表这一部分放到了block块中,其他为母版。
改book_list:
{% extends 'publisher_list.html' %} //这相当于引用了模板的所有内容,包括block中的内容
{% block main %} //所需需要把内容放到自定义的块中
<h2 class="sub-header">书籍列表</h2>
<div class="table-responsive">
<table class="table table-bordered table-hover">
...
</table>
</div>
{% endblock %}
author_list
{% extends 'publisher_list.html' %}
<h1>asdasdasdasdasdadasdasd</h1> //这部分内容不在block中,所以不显示
{% block main %}
<h2>作者列表</h2>
<table class="table table-responsive table-hover"> //稍微改了下table头部
...
</table>
{% endblock %}
但是现在虽然内容变了,但是左侧选择框没变,我们需要调整。
//publisher_list
<div class="col-sm-3 col-md-2 sidebar">
<ul class="nav nav-sidebar">
<li class="{% block pub_active %}active{% endblock %}"><a href="/publisher_list/">出版社列表</a></li> /吧active也变成block块
<li class="{% block book_active %} {% endblock %}"><a href="/book_list/">书籍列表</a></li>
<li class="{% block author_active %} {% endblock %}"><a href="/author_list/">作者列表</a></li>
</ul>
</div>
//book_list
{% block book_active %}
active //加上自己的active
{% endblock %}
{% block pub_active %}
//去掉pub_active的active
{% endblock %}
//author_list
{% block author_active %}
active
{% endblock %}
{% block pub_active %}
{% endblock %}
现在点击就随意切换了。
总结:母版和继承
多写一个css和js的block块,方便子页面有自己独立的样式。
组件
我们已经把所欲的list页面写好了,但是add等页面还是原来的样子
我们吧add页面只保留上面的导航栏,就可以用到组件,只保留一小部分的页面。
bapublisher_list页面的导航栏部分的代码单独放到template文件夹下,叫nav.html
然后在:
include表示包含。效果相同
改publisher_add文件,删除左侧内容,include导航栏:
静态文件
//settings中的静态文件是这样定义的
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static')
]
//在html页面中使用
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="/static/plugins/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/dashboard.css">
</head>
当我们的STATIC_URL = ‘/static/’ 换一个名字后,我们所有的html里面的内容都得手动换,所以有简单的方式
STATIC_URL = ‘/xx/’
可见我们的样式就没有了。
我们只需要这样引入static文件就可以了。也可以<link rel="stylesheet" href="{% get_static_prefix %}css/dashboard.css">
自动获取我们settongs中的配置。
simaple_tag的定义
和自定义filter类似,只不过接收更灵活的参数。他是标签用法和过滤器的用法略有不同,不能写在{% if/for %}中
//tags.py
\@register.simple_tag
def str_join(*args,**kwargs):
return "{}_{}".format('_'.join(args),'*'.join(kwargs.values()))
//template_test.html
{% load my_tags %}
{% str_join 'a' 'b' 'c' k1='d' k2='e' k3='f' %} 和filter的{{}} 不同
inclusion_tag
多用于返回html代码片段,simple_tag也可以,不过很麻烦。
新增页码功能
//tag.py
from django import template
from django.utils.safestring import mark_safe
register = template.Library()
@register.simple_tag
def pagination(num):
li_list = ['<li><a href="#">{}</a></li>'.format(i) for i in range(1,num+1)]
return mark_safe("""<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{}
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>""".format(''.join(li_list)))
//publisher_list
{% block main %}
<h2 class="sub-header">出版社列表</h2>
<a class="btn btn-primary btn-sm" href="/publisher_add/">新增</a>
<div class="table-responsive">
</div>
{% load tags %}
{% pagination 3 %} 加上这两行
{% endblock %}
上面定义了3,则只有3个数字。不过这种方式是很麻烦的。
//tags.py
@register.inclusion_tag('page.html') 使用内置标签,将num参数传给'page.html'
def pagination(num):
return {'num': range(1,num+1)}
//page.html
<nav aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" aria-label="Previous">
<span aria-hidden="true">«</span>
</a>
</li>
{% for li in num %}
<li><a href="#">{{ li }}</a></li> duinum参数进行循环
{% ednfor %}
<li>
<a href="#" aria-label="Next">
<span aria-hidden="true">»</span>
</a>
</li>
</ul>
</nav>
//publisher_list
{% block main %}
<h2 class="sub-header">出版社列表</h2>
<a class="btn btn-primary btn-sm" href="/publisher_add/">新增</a>
<div class="table-responsive">
</div>
{% load tags %}
{% pagination 3 %} 调用函数传参数
{% endblock %}
//book_list
{% block main %}
<h2 class="sub-header">书籍列表</h2>
<div class="table-responsive">
。。
</div>
{% load tags %}
{% pagination 10 %} 10页
{% endblock %}
//author_list
{% block main %}
<h2>作者列表</h2>
<table class="table table-responsive table-hover">
...
</table>
{% load tags %}
{% pagination 2 %} 2页
{% endblock %}
视图
Django的View(视图)
一个视图函数(类),简称视图,是一个简单的Python 函数(类),它接受Web请求并且返回Web响应。
响应可以是一张网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片。
无论视图本身包含什么逻辑,都要返回响应。代码写在哪里也无所谓,只要它在你当前项目目录下面。除此之外没有更多的要求了——可以说“没有什么神奇的地方”。为了将代码放在某处,大家约定成俗将视图放置在项目(project)或应用程序(app)目录中的名为views.py的文件中。
FBV和CBV
我们之前使用的方式都是FBV
FBV function base view
CBV class base view
具体选择看情况而定
对比:
# FBV版添加班级
def add_class(request):
if request.method == "POST":
class_name = request.POST.get("class_name")
models.Classes.objects.create(name=class_name)
return redirect("/class_list/")
return render(request, "add_class.html")
# urls.py
url(r'^author_edit/', views.author_edit), //views.方法名
# CBV版添加班级
from django.views import View
class AddClass(View):
def get(self, request):
return render(request, "add_class.html")
def post(self, request):
class_name = request.POST.get("class_name")
models.Classes.objects.create(name=class_name)
return redirect("/class_list/")
# urls.py
url(r'^add_class/$', views.AddClass.as_view()), //views.类名.as_view()
CBV的方式更清晰规范,具体功能二者相同。
我们测试更改新增出版社这个功能:
//views.py
# 新增出版社CBV
from django.views import View
class PublisherAdd(View):
def get(self,request):
print('get')
return render(request, 'publisher_add.html')
def post(self,request):
print('post')
pub_name = request.POST.get('pub_name')
print(pub_name)
if not pub_name:
return render(request, 'publisher_add.html', {'error': '出版社名不能为空'})
if models.Publisher.objects.filter(name=pub_name):
return render(request,'publisher_add.html',{'error':'出版社名已存在'})
ret = models.Publisher.objects.create(name=pub_name)
return redirect('/publisher_list/')
//urls.py
# url(r'^publisher_add/', views.publisher_add),
url(r'^publisher_add/', views.PublisherAdd.as_view()),
测试访问。
可见会自动识别我们使用的方法,post请求会直接执行类中的post函数。
as_view()方法的流程
views.PublisherAdd.as_view())
看源码帮助我们了解这个过程
- 首先运行项目的时候加载urls.py文件,执行 as_views()方法
@classonlymethod
def as_view(cls, **initkwargs): - as_view()是一个类方法,内部定义并返回了返回了一个view函数:return view
- 当请求到来的时候,执行view函数:
- 首先实例化一个PublisherAdd类对象赋给self:self = cls(**initkwargs)
- 把wsgi的request请求赋给这个对象:self.request = request
- 执行self.dispatch方法 :return self.dispatch(request, *args, **kwargs)
- dispatch方法对用户的请求方法进行了判断
- 允许则通过反射获取对应的方法:handler = getattr(self, request.method.lower(),不允许则返回 handler = self.http_method_not_allowed
def dispatch(self, request, *args, **kwargs): # Try to dispatch to the right method; if a method doesn't exist, # defer to the error handler. Also defer to the error handler if the # request method isn't on the approved list. if request.method.lower() in self.http_method_names: 判断方法是否允许的 handler = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed 不是则返回405 return handler(request, *args, **kwargs)
这里要注意http_method_names是view()方法定义好的八种方法,但是当我们自己在类中定义的时候会优先取我们自己定义的,比如:
class PublisherAdd(View):
http_method_names = ['get', ]
我们只定义允许get请求。
那我们点击这个提交的post请求就会被拒绝
CBV加装饰器
添加一个统计时间的装饰器。
import time
def timer(func):
def inner(*args,**kwargs):
start = time.time()
ret = func(*args,**kwargs)
print('执行时间是{}'.format(time.time()-start))
return ret
return inner
- 第一种方式,对个别方法添加装饰器
method_decorator不会吧self这个对象参数也穿进去,而直接使用@timer就会。
from django.utils.decorators import method_decorator
class PublisherAdd(View):
# http_method_names = ['get', ]
@method_decorator(timer)
def get(self,request):
print('get')
return render(request, 'publisher_add.html')
- 第二种方式,对所有方法加装饰器
from django.utils.decorators import method_decorator
class PublisherAdd(View):
# http_method_names = ['get', ]
@method_decorator(timer)
def dispatch(self, request, *args, **kwargs):
ret =super().dispatch(request, *args, **kwargs) #执行View中的dispatch方法
return ret
# @method_decorator(timer)
def get(self,request):
这样对dispatch方法加装饰器就可以。
- 第三种方式,对类加装饰器
@method_decorator(timer,name='post')
@method_decorator(timer,name='get')
class PublisherAdd(View):
- 第四种方式
@method_decorator(timer,name='dispatch')
class PublisherAdd(View):
类似于第二种方式
而FBV的方式就直接@timer加到上面就行了
补充:装饰器
from functools import wraps
import time
def timer(func):
def inner(*args,**kwargs):
start = time.time()
ret = func(*args,**kwargs)
print('执行时间是{}'.format(time.time()-start))
return ret
return inner
@timer
def func():
"""
funcx相关信息
:return:
"""
time.sleep(0.5)
print('func')
func()
print(func.__name__)
print(func.__doc__)
可以看出实际上加装饰器后变成了返回的inner函数
def timer(func):
@wraps(func) 加上wraps这个装饰器
def inner(*args,**kwargs):
start = time.time()
ret = func(*args,**kwargs)
print('执行时间是{}'.format(time.time()-start))
return ret
return inner
则打印出了func的,推荐使用这种方式。
request对象和response对象
当一个页面被请求时,Django就会创建一个包含本次请求原信息的HttpRequest对象。
Django会将这个对象自动传递给响应的视图函数,一般视图函数约定俗成地使用wsgi封装 request 参数承接这个对象请求成request对象。
request
请求相关的属性
用法 | 含义 |
---|---|
request.method | 请求方法 GET,POST |
request.GET | URL上携带的参数?v1=k1&v2=k2 类似于字典 |
request.POST | post请求提交的数据,编码方式是URLencode,才有数据,但是body中肯定有 |
request.path_info | 返回用户访问url,不包括域名ip,端口,以及GET参数 |
request.body | 请求体,byte类型 request.POST的数据就是从body里面提取到的,GET方法没有请求体, |
scheme | 表示请求方案的字符串(通常为http或https) |
encodeing | 一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 ‘utf-8’)。 这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。 |
COOKIES | 一个标准的Python 字典,包含所有的cookie。键和值都为字符串。 |
FILES | 一个类似于字典的对象,包含所有的上传文件信息。 FILES 中的每个键为 中的name,值则为对应的数据。注意,FILES 只有在请求的方法为POST 且提交的 带有enctype=“multipart/form-data” 的情况下才会包含数据。否则,FILES 将为一个空的类似于字典的对象。 |
META | 一个标准的Python 字典,包含所有的HTTP 首部。具体的头部信息取决于客户端和服务器 |
user | 一个 AUTH_USER_MODEL 类型的对象,表示当前登录的用户。 |
session | 一个既可读又可写的类似于字典的对象,表示当前的会话。只有当Django 启用会话的支持时才可用。完整的细节参见会话的文档。 |
请求相关的方法:
Request.get_host() | 返回请求的原始主机,例如:"127.0.0.1:8000"注意:当主机位于多个代理后面时,get_host() 方法将会失败 |
get_full_path() | 返回 path,如果可以将加上查询字符串。例如:"/music/bands/the_beatles/?print=true" |
is_secure() | 如果请求时是安全的,则返回True;即请求通是过 HTTPS 发起的。 |
is_ajax() | 如果请求是通过XMLHttpRequest 发起的,则返回True,方法是检查 HTTP_X_REQUESTED_WITH 相应的首部是否是字符串’XMLHttpRequest’。大部分现代的 JavaScript 库都会发送这个头部。如果你编写自己的 XMLHttpRequest 调用(在浏览器端),你必须手工设置这个值来让 is_ajax() 可以工作。 |
response
from django.shortcuts import render,redirect,HttpResponse
HttpResponse(‘字符串’) | 返回字符串对象 |
render(request,‘publisher_list.html’,{‘publishers’:all_publishers}) | 返回一个html页面 |
redirect(’/publisher_list/’) | 重定向 |
render是如何实现的:
def render(request, template_name, context=None, content_type=None, status=None, using=None):
"""
Returns a HttpResponse whose content is filled with the result of calling
django.template.loader.render_to_string() with the passed arguments.
"""
content = loader.render_to_string(template_name, context, request, using=using)
return HttpResponse(content, content_type, status)
实际上市Django帮我们做了一个渲染,然后在返回一个 HttpResponse,我们发现也可以直接在HTML页面中使用request.去进行渲染
rediert是如何实现的:
我们看到是添加了一个Locaton的请求头以及一个301/302的状态码,我们再去访问publisher_list这个地址。
我们可以换成:
# return redirect('/publisher_list/')
ret = HttpResponse('', status=301) 手动重定向,status必须设置为301 302
ret['Location'] = '/publisher_list/'
return ret
一些属性HttpResponse.content:响应内容
HttpResponse.charset:响应内容的编码
HttpResponse.status_code:响应的状态码
JsonResponse对象
import json
def get_json(request):
# return HttpResponse({'k1':'v1'}) //只返回k1
return HttpResponse(json.dumps({'k1':'v1'}))
我们就可以用Django自带的功能实现
from django.http import JsonResponse
def get_json(request):
data = {'k1':'v1'}
return JsonResponse(data)
data = [1,2,3]
return JsonResponse(data,safe=False)
返回非字典类型内容的时候需要加上safe=False参数,不然会报错
返回一个json对象
上传文件
url(r'^upload/', views.Upload.as_view()),
class Upload(View):
def get(self,request):
return render(request,'upload.html')
<form action="" method="post" enctype="multipart/form-data"> 更换编码方式,才可以通过request.Files看见内容
{% csrf_token %}
<input type="file" name = "f1">
<button>上传</button>
</form>
在写我们的post方法。
def get(self,request):
return render(request,'upload.html')
def post(self,request):
print(request.POST) <QueryDict: {'csrfmiddlewaretoken': ['06AeCUIEEBreRGCfz0b3eLtDhXAFQ02ph5Zo0WVonPjlp55PYSOapE1RMiVZvP0E']}>
print(request.FILES) <MultiValueDict: {'f1': [<InMemoryUploadedFile: MeetNowUninstall.log (application/octet-stream)>]}>
//可以看见request.FILES这个字典对象文件InMemoryUploadedFile
file = request.FILES.get('f1')
print(file,type(file),file.name)
//MeetNowUninstall.log <class 'django.core.files.uploadedfile.InMemoryUploadedFile'> MeetNowUninstall.log
file1 = request.FILES['f1'] 这两种方式都可以
print(file1,type(file1),file1.name)
return HttpResponse('ok')
with open(file.name,'wb') as f:
for i in file:
f.write(i)
return HttpResponse('ok')
文件就上传进去了。图片,视频也是一样,都是通过二进制上传进去 的。
路由
Django的路由系统:URL配置(URLconf),也就是urls.py这个文件。就像Django所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表。
我们就是以这种方式告诉Django,遇到哪个URL的时候,要对应执行哪个函数。
正则表达式
- 基本格式
urlpatterns = [
url(正则表达式, views视图,参数,别名),
]```
url用法只是1.1的用法
```python
#路由
url(r'^blog/',views.blog),
url(r'^blog/2022/',views.blogs),
views.py
def blog(request):
return HttpResponse('it is blog')
def blogs(request):
return HttpResponse('it is blogs')
但是我们访问的时候还是匹配到了blog方法,因为都是以blog开头,所以按照顺序从上往下就先执行了blog方法,而不是blogs,
所以我们要加上 $
现在是静态路由,动态路由就需要正则表达式了:
url(r'^blog/\d{4}/\d{2}/$',views.blogs),
url(r'^blog/[0-9]{4}/[a-z]{2}/$',views.blogs),
注意
- urlpatterns中的元素按照书写顺序从上往下逐一匹配正则表达式,一旦匹配成功则不再继续。
- 若要从URL中捕获一个值,只需要在它周围放置一对圆括号(分组匹配)。
- 不需要添加一个前导的反斜杠,因为每个URL 都有。例如,应该是^articles 而不是 ^/articles。
- 每个正则表达式前面的’r’ 是可选的但是建议加上。
补充
我们url里面写的是/$结尾的,但是我们浏览器访问的时候不加/也会给我们自动重定向到带/的路径,这是Django帮我们做的一件事,可以再setting中控制。
Django settings.py配置文件中默认没有 APPEND_SLASH 这个参数,但 Django 默认这个参数为 APPEND_SLASH = True。 其作用就是自动在网址结尾加’/’。
- APPEND_SLASH=True # 是否开启URL访问地址后面不为/跳转至带有/的路径的配置项
分组和命名分组
分组
url(r'^blog/([0-9]{4})/[a-z]{2}/$',views.blogs), 加了个小括号
def blogs(request,year): // 将括号括起来的内容传参给blogs函数
print(year,type(year)) // 2222 <class 'str'>,得到这个参数
return HttpResponse('it is blogs')
上面的示例使用简单的正则表达式分组匹配(通过圆括号)来捕获URL中的值并以位置参数形式传递给视图。
在更高级的用法中,可以使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图。
在Python的正则表达式中,分组命名正则表达式组的语法是(?P<name>pattern),其中name是组的名称,pattern是要匹配的模式。
每个在URLconf中捕获的参数都作为一个普通的Python字符串传递给视图,无论正则表达式使用的是什么匹配方式
命名分组
下面是以上URLconf 使用命名组的重写:
url(r'^blog/(?P<year>[0-9]{4})/(?P<month>[a-z]{2})/$',views.blogs),
def blogs(request,month,year):
print(year,month)
return HttpResponse('it is blogs')
虽然year和month的顺序不一致,但是还是通过关键字匹配到了值
下面我们改一下publisher_edit的url路径:
url(r'^publisher_edit/(\d+)/', views.publisher_edit),
# 编辑出版社
def publisher_edit(request,*args,**kwargs): 也可以这样直接获取所有的未知参数和关键字参数,分组是位置参数,命名分组是关键字参数。
def publisher_edit(request,pk): #添加参数,正则获取到pk
# print(request)
# pk = request.GET.get('pk') #就不需要之前的方式获取了
<!-- <a class="btn btn-info btn-sm" href="/publisher_edit/?pk={{ i.id }}">编辑</a>-->
<a class="btn btn-info btn-sm" href="/publisher_edit/{{ i.id }}">编辑</a> 替换上面的方式
就可以通过位置参数匹配到了。
默认参数
url(r'^pages/$',views.page),
url(r'^pages/page(?P<num>[0-9]+)/$',views.page),
def page(request,num='1'): num默认等于1
return HttpResponse(num)
在上面的例子中,两个URL模式指向相同的view - views.page - 但是第一个模式并没有从URL中捕获任何东西。
如果第一个模式匹配上了,page()函数将使用其默认参数num=“1”,如果第二个模式匹配,page()将使用正则表达式捕获到的num值。
传递额外的参数给视图函数(了解)
url(r'^blog/(?P<year>[0-9]{4})/(?P<month>[a-z]{2})/$',views.blogs,{'year':'1998'})
后面的这个year是一个关键字参数,会替代前面year的值
include其他的URLconfs
为了便于不同的app开发功能,就需要这种方式:
在app01下创建一个urls目录,
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^blog/(?P<year>[0-9]{4})/(?P<month>[a-z]{2})/$',views.blogs,{'year':'1998'}),
url(r'^pages/$',views.page),
url(r'^pages/page(?P<num>[0-9]+)/$',views.page),
]
把项目中的urls.py中就可以这样写
from django.conf.urls import url,include #导入include
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'app01/',include('app01.urls')) #include app01中的urls.py
url(r'app02/',include('app02.urls')) #include app02中的urls.py
这样我们必须访问到 /app01/pages/ 才可以执行views.page这个方法,app01匹配一部分,app01.urls匹配一部分。
我们也可以不写app01
url(r'',include('app01.urls'))
这样也能访问了,建议加上
命名URL和URL反向解析
静态方式
//urls
url(r'',include('app01.urls'))
//app01/urls
urlpatterns = [
url(r'^blog/$',views.blogs,name='blog'),
url(r'^blog/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.blogs, {'year': '1998'}),
url(r'^pages/$',views.page),
url(r'^pages/page(?P<num>[0-9]+)/$',views.page),
]
无论url里面怎么写,我们都可以通过固定的方式访问到这个地址,类似于起别名。就可以用到反向解析URL、反向URL 匹配。
现在我们需要通过http://127.0.0.1:8000/app01/blog/才能访问到这个地址.
我们创建一个index1.html里面使用a标签指向上面的地址:
<a href="/app01/blog/">博客</a>
我们需要写上完整的路径才可以访问到。
urlpatterns = [
url(r'^blog/$',views.blog,name='blog'), 给blog的地址起一个别名
//html
<a href="{% url blog %}">博客</a> 使用这种方法使用别名
这样的话不论地址改成了什么,我们都可以用别名访问到。
那我们如果想在py文件函数中通过别名拿到这个地址,应该这样做:
views.py
from django.shortcuts import render,HttpResponse,reverse
from django.urls import NoReverseMatch, reverse 这两种方式都行
def blog(request):
url = reverse('blog')
print(url,type(url))
return HttpResponse('it is blog')
就访问到了。
分组
url(r'^blog/([0-9]{4})/([0-9]{2})/$', views.blogs, name='blogs'), 起名为blogs
<a href="{% url 'blogs' '2022' '02' %}">blogs</a> 这种方式去传参
py文件:
def blogs(request,month,year):
print(year,month)
url = reverse('blogs',args=('2018','11'))
print(url,type(url)) /app01/blog/2018/11/ <class 'str'>
return HttpResponse('it is blogs')
命名分组
命名分组和分组用法相同不过多了一种传参的方式:
url(r'^blog/(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', views.blogs, name='blogs'),
<a href="{% url 'blogs' '2022' '02' %}">blogs</a>
<a href="{% url 'blogs' month='11' year='2011' %}">blogs</a> #这种不用注意顺序,通过关键字匹配
上面两种方式都可以了
py文件中使用:
url = reverse('blogs',kwargs={'year':'2111','month':'07'}) 用kwargs,不用args了
总结
namespace
//urls
url(r'app01/',include('app01.urls')),
url(r'app02/',include('app02.urls'))
//app01/urls
url(r'^home/$',views.home)
//app02/urls
url(r'^home/$',views.home)
app01和app02都有共同的name home
index.html
{% url 'home' %}
但是我们访问app01/index的时候却访问到了app02,就要用到namespace了
urls.py
url(r'app01/',include('app01.urls',namespace='app01')),
url(r'app02/',include('app02.urls',namespace='app02')),
各自有各自的命名空间。
不过这是我们在使用反向解析的时候就需要加上namespace的名字才好用:
views.py
def blog(request):
url = reverse('app01:blog')
print(url,type(url))
return HttpResponse('it is blog')
index.html
<a href="{% url 'app01:blog' %}">博客</a>
<a href="{% url 'app01:blogs' '2022' '02' %}">blogs</a>
<a href="{% url 'app01:blogs' month='11' year='2011' %}">blogs</a>
{% url 'app01:home' %}
都得加上namespace的名字
删除功能的合并
通过一个url地址,一个函数,去实现出版社,书籍以及组着的删除。
//urls.py
url(r'^(publisher|book|autor)_del/(\d+)/', views.delete, name='del'),
url(r'^publisher_list/', views.publisher_list,name='publisher'), 起和(publisher|book|autor)里面相同的名字
url(r'^book_list/', views.book_list,name='book'),
url(r'^author_list/', views.author_list,name='author'),
//views.py
def delete(request,name,pk):
print(name,pk)
return HttpResponse('ok')
author/book/publisher_list.html
<a class="btn btn-danger btn-sm" href="{% url 'del' 'publisher' i.pk %}">删除</a>
<a class="btn btn-danger btn-sm" href="{% url 'del' 'book' book.pk %}">删除</a>
<a href="{% url 'del' 'author' author.pk %}">删除</a>
urls.py中用两个分组去匹配功能和对应的id。然后把三个html文件汇总对应的删除按钮的地址链接到del上面,传入book和id参数。
点击删除测试正确。
并且可以获取到对应的值。我们就可以通过获取的对象去执行删除。
# dic = {
# 'publisher' : models.Publisher,
# 'book' : models.Book,
# 'author' : models.Author,
# }
cls = getattr(models,name.capitalize()) 通过getattr反射获得类名,capitalize()首字母大写
print(cls)
cls.objects.filter(pk=pk).delete()
# 返回重定向
# return HttpResponse('ok')
return redirect(reverse(name)) #直接通过reverse获取名字即可
还可以这样用:
url(r'^(\w+)_del/(\d+)/', views.delete, name='del'),
//views.py
if not cls:
return HttpResponse('请检测表名')
ret = cls.objects.filter(pk=pk)
if ret:
ret.delete()
else:
return HttpResponse('要删除的表不存在')
return redirect(reverse(name))
return redirect(name)
补充一点redirect函数可以自动reverseurl的name。
模型 ORM
下面在深入的介绍介绍ORM。
-
简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。ORM在业务逻辑层和数据库层之间充当了桥梁的作用。
-
ORM的优势
ORM解决的主要问题是对象和关系的映射。它通常将一个类和一张表一一对应,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段。
ORM提供了对数据库的映射,不用直接编写SQL代码,只需操作对象就能对数据库操作数据。
让软件开发人员专注于业务逻辑的处理,提高了开发效率。 -
ORM的劣势
ORM的缺点是会在一定程度上牺牲程序的执行效率。
ORM的操作是有限的,也就是ORM定义好的操作是可以完成的,一些复杂的查询操作是完成不了。
ORM用多了SQL语句就不会写了,关系数据库相关技能退化…
新见一个项目进行学习aoout_orm,创建相应的库
常见字段类型
class Person(models.Model):
pid = models.AutoField(primary_key=True) #自己创建的主键,默认为id
name = models.CharField(max_length=32) #varchar32
age = models.IntegerField() #一个整数类型。数值的范围是 -2147483648 ~ 2147483647。
birth = models.DateTimeField(auto_now_add=True) #auto_now_add:新创建对象时自动添加当前日期时间。auto_now:每次修改时修改为当前日期时间。
插入一条数据
D:\Django\about_orm>python manage.py shell
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 27 2018, 04:59:51) [MSC v.1914 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from app01 import models
>>> ret = models.Person.objects.create(name='mom',age=50)
>>> print(ret)
Person object
>>> ret
<Person: Person object>
>>> ret.age=51
>>> ret.save()
>>> ret.age
51
python manage.py shell可直接加载shell环境。
在这里插入的数据是django自动帮我们创建的birth时间。
注意:auto_now和auto_now_add和default参数是互斥的,不能同时设置。
自定义字段
class MyCharField(models.Field): #所有的类型都继承于Field类
"""
自定义的char类型的字段类
"""
def __init__(self, max_length, *args, **kwargs):
self.max_length = max_length
super(MyCharField, self).__init__(max_length=max_length, *args, **kwargs)
def db_type(self, connection):
"""
限定生成数据库表的字段类型为char,长度为max_length指定的值
"""
return 'char(%s)' % self.max_length
class Person(models.Model):
pid = models.AutoField(primary_key=True) #自己创建的主键,默认为id
name = models.CharField(max_length=32) #varchar32
age = models.IntegerField() #一个整数类型。数值的范围是 -2147483648 ~ 2147483647。
birth = models.DateTimeField(auto_now_add=True) #auto_now_add:新创建对象时自动添加当前日期时间。
phone = MyCharField(max_length=11)
执行makemigrations的时候就会提示为已有的数据添加新字段的默认值,或者在models中添加一个default值,给所有数据添加一个默认值。
字段参数
使用django的admin
- 创建超级用户
访问
- 注册models
在app01下的admin.py注册models。
class Person(models.Model):
pid = models.AutoField(primary_key=True)
name = models.CharField(max_length=32)
age = models.IntegerField()
birth = models.DateTimeField(auto_now_add=True)
phone = MyCharField(max_length=11)
def __str__(self):
return '{}-{}'.format(self.name,self.age) //添加这两行,上面才显示 mom-51
参数 | 作用 |
---|---|
null | 数据库中字段是否可以为空 |
db_column | 数据库中字段的列名 |
default | 数据库中字段的默认值 |
primary_key | 数据库中字段是否为主键 |
db_index | 数据库中字段是否可以建立索引 |
unique | 数据库中字段是否可以建立唯一索引 |
unique_for_date | 数据库中字段【日期】部分是否可以建立唯一索引 |
unique_for_month | 数据库中字段【月】部分是否可以建立唯一索引 |
unique_for_year | 数据库中字段【年】部分是否可以建立唯一索引 |
verbose_name | Admin中显示的字段名称 |
blank | Admin中是否允许用户输入为空 |
editable | Admin中是否可以编辑 |
help_text | Admin中该字段的提示信息 |
choices | Admin中显示选择框的内容(性别),用不变动的数据放在内存中从而避免跨表操作,如:gf = models.IntegerField(choices=[(0, ‘何穗’),(1, ‘大表姐’),],default=1) |
error_messages | 自定义错误信息(字典类型),从而定制想要显示的错误信息; 字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date如:{‘null’: “不能为空.”, ‘invalid’: ‘格式错误’} |
validators | 自定义错误验证(列表类型),从而定制想要的验证规则 |
每次添加字段参数后都需要python manage.py makemigrations以及python manage.py migrate 才能生效
表的参数
class Person(models.Model):
pid = models.AutoField(primary_key=True) #自己创建的主键,默认为id
name = models.CharField(max_length=32,null=True,blank=True) #varchar32
def __str__(self):
return '{}-{}'.format(self.name,self.age)
//定义一个类嵌套在person类中
class Meta:
# 数据库中生成的表名称 默认 app名称 + 下划线 + 类名 app01_Person
db_table = "table_name"
# admin中显示的表名称
verbose_name = '个人信息'
# verbose_name加s
verbose_name_plural = '所有用户信息'
# 联合索引
index_together = [
("name", "age"), # 应为两个存在的字段
]
# 联合唯一索引
unique_together = (("name", "age"),) # 应为两个存在的字段,添加了唯一约束
verbose_name_plural对应这里。
verbose_name对应这里。此时库里的表名已经变成了person。
必知必会13条
在django console中的方式执行查询,退出后就不保存了,
我们可以:
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
import django
django.setup() 这四行就可以让我们去执行了
from app01 import models
#all查询所有数据, QuerySet 对象列表[对象,对象]
ret = models.Person.objects.all()
print(ret) ///显示 <QuerySet [<Person: aom-23>, <Person: mom-51>, <Person: None-5>]>,列表类型调用了__repr_
#get 获取唯一一个对象,没有或者多个报错
ret = models.Person.objects.get(pk=1)
print(ret) ///显示 aom-23 直接print调用了我们定义的__str__
#filter 获取满足条件的数据 对象列表
ret = models.Person.objects.filter(age=18)
print(ret) ///<QuerySet [<Person: a-18>, <Person: s-18>]>
#order by
ret = models.Person.objects.all().order_by('age')
# ret = models.Person.objects.all().order_by('-age') #降序
# ret = models.Person.objects.all().order_by('-age').reberse() #对已经排序的列进行反转
# ret = models.Person.objects.all().order_by('-age','pid') #多字段排序
print(ret)
#value,一个字典列表,拿到对象的指定属性,不指定则拿到所有属性
ret = models.Person.objects.all().values('pid','name')
# ret = models.Person.objects.all().values_list('pid','name') #编程元组列表了,不显示字段名了
for i in ret:
print(i)
# distinct 去重,.all()可以省略,默认拿的也是all
ret = models.Person.objects.all().values('age').distinct()
#count 计数
ret = models.Person.objects.all().count()
#first 首个对象
ret = models.Person.objects.all().first()
#last 最后一个对象
ret = models.Person.objects.all().last()
#exists() 判断是否有结果
ret = models.Person.objects.filter(pk=10).exists()
#exclude 排除某个结果
ret = models.Person.objects.exclude(pk=1)
print(ret)
总结
单表的双下划线
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
import django
django.setup()
from app01 import models
ret = models.Person.objects.filter(age=18)
print(ret)
ret = models.Person.objects.filter(age__lt=18)
print(ret)
ret = models.Person.objects.filter(age__gte=18)
print(ret)
ret = models.Person.objects.filter(pid__in=[1,2,3,4])
print(ret)
ret = models.Person.objects.filter(age__range=[40,60])
print(ret)
ret = models.Person.objects.filter(name__contains='aom') #like
# ret = models.Person.objects.filter(name__icontains='aom') #like i忽略大小写
print(ret)
ret = models.Person.objects.filter(name__startswith='aom') #以什么开头
# ret = models.Person.objects.filter(name__istartswith='aom') #i忽略大小写
# ret = models.Person.objects.filter(name__endswith='aom')
# ret = models.Person.objects.filter(name__iendswith='aom')
print(ret)
ret = models.Person.objects.filter(name__isnull=True) #以什么开头
print(ret)
#对于datetime类型的数据还可以
ret = models.Person.objects.filter(birth__year='2019') #以什么开头
print(ret)
外键的操作
对前面的外键操作进行补充。依然使用出版社和书本做演示
class Publisher(models.Model):
name = models.CharField(max_length=32,verbose_name='出版社名称')
def __str__(self):
return '<Publisher object: {}-{}>'.format(self.pk,self.name)
class Book(models.Model):
name = models.CharField(max_length=32,verbose_name='书名')
pub = models.ForeignKey('Publisher',on_delete=models.CASCADE)
# pub = models.ForeignKey('Publisher',on_delete=models.CASCADE,related_name='books')
def __str__(self):
return '<Book object: {}-{}>'.format(self.pk,self.name)
#基于对象的查询
## 正向查询,从书籍拿到对应的外键出版社
book_object = models.Book.objects.get(pk=1)
print(book_object)
print(book_object.pub) #关联的出版社对象
print(book_object.pub_id) #关联的出版社对象的id
## 反向查询,从一个出版社获取多个书籍
pub_obj = models.Publisher.objects.get(pk=3)
print(pub_obj)
print(pub_obj.book_set,type(pub_obj.book_set)) #定义好的类名,是个关系管理对象: 类名小写_set
print(pub_obj.book_set.all()) #拿到对应的所有书籍
# 对象管理
pub_obj = models.Publisher.objects.get(pk=3)
# print(pub_obj)
## set,add,create外键不可以用id,只可以用对象的方式
pub_obj.books.set(models.Book.objects.filter(id_in=[1,2,3]))
pub_obj.books.add(*models.Book.objects.all())
## remove clear 移除外键的关系,必须设置外键 null=True属性才可以使用这两个方法
pub_obj.books.clear()
这里的book_set是已经定义好的一个类,我们可以通过related_name=‘books’ 去设置它的名称,就没有类名小写_set的写法了
pub = models.ForeignKey('Publisher',on_delete=models.CASCADE,related_name='books')
pub = models.ForeignKey('Publisher',on_delete=models.CASCADE,related_name='books',related_query_name='book')
指定related_name='books’不指定related_query_name时使用related_name的值
指定related_name='books’并指定related_query_name时使用related_query_name的值
多对多的操作
多对多的操作用ManyToManyField,django会自动创建第三张多对多的表
class Author(models.Model):
name = models.CharField(max_length=32,verbose_name='姓名')
books = models.ManyToManyField('Book')
author_obj = models.Author.objects.get(pk=2)
print(author_obj.books) #关系管理对象
print(author_obj.books.all()) #关系管理对象的集合
book_obj = models.Book.objects.get(pk=2)
print(book_obj)
print(book_obj.author_set.all()) #用法和外键相同都是author_set类型的
ret = models.Book.objects.filter(author__name='aom')
print(ret)
ret = models.Author.objects.filter(books__name='爱她')
print(ret)
这里也可以用ralate_name ,用法相同上面。
# 关系管理对象的方法
## all查询所有对象
## set设置多对多关系 [2,3]
## add添加多对多关系
author_obj = models.Author.objects.get(pk=2)
author_obj.books.set([2,3]) #修改对应的书籍
# author_obj.books.set(models.Book.objects.filter(id__in=[2,3])) #修改对应的书籍
author_obj.books.add(4)
author_obj.books.add(*models.Book.objects.filter(id__in=[2,3])) #这里不能用李彪,所以用*吧列表拆分
## remove 删除关系
author_obj.books.remove(4) 删除pk=2的作者对象对应的id为4的书籍
author_obj.books.remove(*models.Book.objects.filter(id__in=[2,3]))
## clear清空多对多关系
author_obj.books.clear() ,外键字段必须为null=True才可清除
## create 通过作者创建新书籍,并与这个书籍建立关系
author_obj.books.create(name='桃园外传',pub_id=1)
通过书籍创建作者也是同样的方式
通过出版社拿到author信息。
ret = models.Author.objects.filter(books__pub__name='出版社1').distinct() 去重
print(ret)
通过books__pub__name找到出版社对象为出版社1的所有作者。
聚合和分组查询
aggregate()是QuerySet 的一个终止子句,意思是说,它返回一个包含一些键值对的字典。
# 聚合aggregate max和min为聚合值的指定名称,不指定则为price_max
# ret = models.Book.objects.filter(id__gt=2).aggregate(max=Max('price'),min=Min('price'))
# print(ret)
# 分组 group by 配合聚合使用
## 例子1 统计每一本书的作者数
ret = models.Book.objects.all()
print(ret)
ret = models.Book.objects.annotate(Count('authors'))
print(ret)
ret = models.Book.objects.annotate(Count('authors')).values() #添加了额外信息进结果
for i in ret:
print(i)
聚合信息放入了authors_count字段。
##例子2 统计每个出版社最便宜的书
###方法1
# ret = models.Publisher.objects.annotate(Min('books__price')).values()
# for i in ret:
# print(i)
##方法2,按照pub_id,pub_name进行分组
ret = models.Book.objects.values('pub_id','pub__name').annotate(Min('price'))
for i in ret:
print(i)
## 统计不止一个作者的书籍
ret = models.Book.objects.annotate(count=Count('authors')).values()
for i in ret:
print(i)
ret = models.Book.objects.annotate(count=Count('authors')).filter(count__gt=1)
print(ret)
## 对书籍的作者数进行排序
ret = models.Book.objects.annotate(count=Count('authors')).order_by('-count')
print(ret)
## 查询各个作者出的书的总价格
ret = models.Author.objects.annotate(sum=Sum('books__price')).values()
for i in ret:
print(i)
ret = models.Book.objects.values('authors','authors__name').annotate(sum=Sum('price'))
for i in ret:
print(i)
F查询和Q查询
在上面所有的例子中,我们构造的过滤器都只是将字段值与某个常量做比较。如果我们要对两个字段的值做比较,那该怎么做呢?
Django 提供 F() 来做这样的比较。F() 的实例可以在查询中引用字段,来比较同一个 model 实例中两个不同字段的值。
filter() 等方法中的关键字参数查询都是一起进行“AND” 的。 如果你需要执行更复杂的查询(例如OR语句),你可以使用Q对象。
Book表新增两列。
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
import django
from django.db.models import F,Q
django.setup()
from app01 import models
#找出sale大于库存的
ret = models.Book.objects.filter(sale__gt=F('kucun')) #where 'sale' > 'kucun'
print(ret)
# 吧id<3的书的sale的量乘2假13
ret = models.Book.objects.filter(id__lte=3).update(sale=F('sale') * 2 + 13)
#找到id小于三或者id大于5 的书籍
ret = models.Book.objects.filter(Q(id__lt=2)|Q(id__gt=3))
print(ret)
|是或,&是与,~是非。还可以套娃Q(Q(id__lt=2)|Q(id__gt=3))
~Q(name__startswith=‘香’)
事务
什么是事务? 原子性 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "about_orm.settings")
import django
from django.db.models import F,Q
django.setup()
from app01 import models
from django.db import transaction
with transaction.atomic():
models.Book.objects.update(kucun=F('kucun')-10)
int('sss') #在这里会报错
models.Book.objects.update(sale=F('sale')+10)
虽然 models.Book.objects.update(kucun=F(‘kucun’)-10)已经执行了,但是由于事务的一致性,中途出错了,前面的部分也不会生效
注释掉后九成宫了:
一般为了代码的正常运行,我们可以吧事务加到try里面。不可以它try放到事务里面
cookies操作
保存在浏览器的一组键值对
特性:
1. 由服务器让浏览器进行设置的
2. cookies信息保存在浏览器本地,浏览器有权不保存
3. 浏览器再次访问的时候自动携带对应的cookies信息
登录后保存登录状态
给图书管理系统加上登录功能。当前我们不登录也可以进行增删改查的工作,我们想要在登录后才能就进行操作。
url(r'^login/', views.login),
def login(request):
if request.method == 'POST':
user = request.POST.get('user')
pwd = request.POST.get('pwd')
if user == 'alex' and pwd == '123':
return redirect('publisher')
else:
error = '用户名或密码错误'
return render(request,'login.html',locals())
<form action="" method="post">
{% csrf_token %}
<p>
用户名:<input type="text" name="user">
</p>
<p>
密码:<input type="passowrd" name="pwd">
</p>
<p>{{ error }}</p>
<button>登录</button>
</form>
我们可以做判断用户是否登录,登录才可以进行操作。
当前我们的每个请求之间都是独立的,没有任何的关系,我们可以用cookies保存上一次登录的状态信息。
Django中如何操作cookie
def login(request):
if request.method == 'POST':
user = request.POST.get('user')
pwd = request.POST.get('pwd')
if user == 'alex' and pwd == '123':
#登录成功后保存登录状态到cookie中
ret = redirect('publisher')
ret.set_cookie('is_login','1') //这一步
return ret
else:
error = '用户名或密码错误'
return render(request,'login.html',locals())
可见登录的时候服务器给浏览器了一个cookie,浏览器第二次访问的时候就携带上了这个cookie。 print(request.COOKIES)打印cookies信息
def publisher_list(request):
all_publishers = models.Publisher.objects.all().order_by('id')
print(request.COOKIES)
is_login = request.COOKIES.get('is_login')
if is_login != '1':
#没有登录
return redirect('/login/')
return render(request,'publisher_list.html',{'publishers':all_publishers})
没有登陆就直接重定向到了登录页面。那么对于publisher我们就实现了这个功能。。我们可以做成装饰器,给每个页面加上这个功能。
def login_required(func):
@wraps(func)
def inner(request,*args,**kwargs):
print(request.COOKIES)
is_login = request.COOKIES.get('is_login')
if is_login != '1':
# 没有登录
return redirect('/login/')
ret = func(request,*args,**kwargs)
return ret
return inner()
@login_required #加到想加的页面即可
def publisher_list(request):
登录后跳转到上次登录的界面
def login_required(func):
@wraps(func)
def inner(request,*args,**kwargs):
print(request.COOKIES)
is_login = request.COOKIES.get('is_login')
if is_login != '1':
# 没有登录
# return redirect('/login/')
return redirect('/login/?url={}'.format(request.path_info)) #装饰器中定义好url,在login后获取
ret = func(request,*args,**kwargs)
return ret
return inner
def login(request):
if request.method == 'POST':
user = request.POST.get('user')
pwd = request.POST.get('pwd')
if user == 'alex' and pwd == '123':
url = request.GET.get('url') #获取上次的url
if url:
return_url = url
else:
return_url = reverse('publisher') #没有则返回publisher_list界面
#登录成功后保存登录状态到cookie中
# ret = redirect('publisher')
ret = redirect(return_url)
ret.set_cookie('is_login','1')
return ret
else:
error = '用户名或密码错误'
return render(request,'login.html',locals())
cookies操作
设置加密cookies
def login_required(func):
@wraps(func)
def inner(request,*args,**kwargs):
print(request.COOKIES)
# is_login = request.COOKIES.get('is_login')
is_login = request.get_signed_cookie('is_login',salt='aom',default='') #加密的cookie必须这样获取
print(is_login)
。。。。
return inner
def login(request):
if request.method == 'POST':
。。。
ret.set_signed_cookie('is_login','1',salt='aom') #使用加密cookie
return ret
else:
error = '用户名或密码错误'
return render(request,'login.html',locals())
获取Cookie,获取cookie在请求头中:
request.COOKIES[‘key’]
request.get_signed_cookie(‘key’,default=RAISE_ERROR, salt=’’, max_age=None)
get_signed_cookie方法的参数:
- default: 默认值
- salt: 加密盐
- max_age: 后台控制过期时间
设置Cookie,设置cookie在响应头中
rep = HttpResponse(…)
rep = render(request, …)
rep.set_cookie(key,value,…)
rep.set_signed_cookie(key,value,salt=‘加密盐’,…)
参数:
- key, 键
- value=’’, 值
- max_age=None, 超时时间
- expires=None, 超时时间(IE requires expires, so set it if hasn’t been already.)
- path=’/’, Cookie生效的路径,/ 表示根路径,特殊的:根路径的cookie可以被任何url的页面访问
- domain=None, Cookie生效的域名
- secure=False, https传输
- httponly=False 只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖
设置path参数后,我们登录及book_list页面后在调到publisher_list页面就会要求重新登录,因为cookies没有生效
设置httponly参数后我们在浏览器的console中九五发用document.cookie获取了
清除cookie
def logout(request):
#清除cookies,清除某个键值对
ret=redirect('/login/')
ret.delete_cookie('is_login')
#重定向到登录页面
return ret
具体实现就是把is_login的值设置为空,然后超市时间设置为0,然后就立马失效了。