views.py
中的。函数视图定义方式:
"""
1. 函数视图它是一个标准的Python函数。
2. 函数视图中,第一个参数必须定义:第一个参数为请求对象,用于接收用户发送的请求报文。
3. 函数视图中,必须返回响应对象:用于构造响应报文,并响应给用户。
4. 说明:
请求对象:HttpRequest() 对应的对象
响应对象:HttpResponse() 对应的对象
"""
from django.shortcuts import render
from django import http
# Create your views here.
def register(request):
"""
用户注册函数视图
:param request: 请求对象,包含了请求报文信息
:return: 响应对象,用于构造响应报文,并响应给用户
"""
# 响应数据
return http.HttpResponse('这里假装返回注册页面')
提示:
问题:
解决:
需求:
http://127.0.0.1:8000/users/register/
**访问用户注册视图1. 新建子路由文件
- 在**
子应用
中新建一个urls.py
**文件用于定义该应用的所有路由信息
2. 注册子路由
- 在**
子应用/urls.py
**文件中定义路由信息
from django.urls import path
from . import views
# urlpatterns是被Django自动识别的路由列表变量:定义该应用的所有路由信息
urlpatterns = [
# 函数视图路由语法:
# path('网络地址非正则表达式', 函数视图名),
# 第一个参数写的是资源路径
# 用户注册:http://127.0.0.1:8000/users/register/
path('users/register/', views.register),
# 除了path 还有re_path() 能使用正则表达式的资源路径 要写好^和$
]
3. 注册总路由
- 在工程总路由**
工程同名目录/urls.py
**中包含子应用的路由数据
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
# 自带的后台管理系统的总路由:可以忽略
path('admin/', admin.site.urls),
# 总路由包含子路由语法
# path('网络地址前缀/', include('子应用.urls')),
# 或者
# path('', include('子应用.urls')),
# 用户模块:http://127.0.0.1:8000/users/register/
path('', include('users.urls')),
]
总路由说明:
include()
来将users子应用
**里的所有路由都包含进工程总路由中。4. 启动运行测试
重新启动django程序
python manage.py runserver
使用postman进行请求测试: http://127.0.0.1:8000/users/register/
拓展
r
转义字符
一般会在路由地址之前添加一个r来防止有\
造成地址出错
path()和re_path()
django里一般情况下使用path已经能解决大部分路由路径需求,虽然不能使用正则表达式,但是可以使用路径转换器来达成和正则差不多的效果且代码可读性提高。
但是依旧有需要使用正则的时候,此时我们就使用re_path()来匹配路由路径
需求:
http://127.0.0.1:8000/users/register/
**发送GET请求,用来获取注册页面。http://127.0.0.1:8000/users/register/
**发送POST请求,用来实现注册逻辑。需求实现:
def register(request):
"""
用户注册函数视图
:param request: 请求对象,包含了请求报文信息
:return: 响应对象,用于构造响应报文,并响应给用户
"""
# 获取请求方法,判断是GET还是POST请求
if request.method == 'GET':
# 处理GET请求,返回注册页面
return HttpResponse('这里假装返回注册页面')
else:
# 处理POST请求,实现注册逻辑
return HttpResponse('这里假装实现注册逻辑')
函数视图问题说明:
解决方案:
类视图定义方式:
1. 类视图它是一个标准的Python类。
2. 类视图需要继承自Django提供的父类视图View。
3. 在类视图中,
3.1 需要定义跟请求方法同名的函数来对应不同请求方式
3.2 在请求方法同名的函数中,还必须定义一个接收请求的参数(同函数视图)
3.3 在请求方法同名的函数中,还必须返回一个响应对象(同函数视图)
from django.views import View
class RegisterView(View):
"""用户注册类视图
http://127.0.0.1:8000/users/register/
"""
def get(self, request):
"""
处理GET请求,返回注册页面
:param request: 请求对象,包含了请求报文信息
:return: 响应对象,用于构造响应报文,并响应给用户
"""
return http.HttpResponse('这里假装返回注册页面')
def post(self, request):
"""
处理POST请求,实现注册逻辑
:param request: 请求对象,包含了请求报文信息
:return: 响应对象,用于构造响应报文,并响应给用户
"""
return http.HttpResponse('这里假装实现注册逻辑')
类视图的好处:
- 代码可读性好
- 类视图相对于函数视图有更高的复用性, 如果其他地方需要用到某个类视图的某个特定逻辑,直接继承该类视图即可。
说明:
需求:
http://127.0.0.1:8000/users/register/
**发送GET请求,用来获取注册页面。http://127.0.0.1:8000/users/register/
**发送POST请求,用来实现注册逻辑。1. 注册子路由
- 在**
子应用/urls.py
**文件中定义路由信息- 由于当前代码还是编写在users子应用中的,所以总路由注册过一次之后,不用再注册
from django.urls import path
from . import views
# urlpatterns是被Django自动识别的路由列表变量:定义该应用的所有路由信息
urlpatterns = [
# 类视图路由语法:
# path('网络地址正则表达式', 类视图.as_view()),
# 用户注册:http://127.0.0.1:8000/users/register/
path('users/register/', views.RegisterView.as_view()),
]
2. 启动运行测试
settings.py
文件中注释掉CSRF中间件
# 中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
python manage.py runserver
http://127.0.0.1:8000/users/register/
class View:
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
# 为所有视图定义简单的父类,只实现了请求方法分派和简单的完整性检查。
"""
# 定义Django允许接收的请求方法
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
# 类视图的初始化构造函数,创建类视图对象时会被调用,并可以接收额外的参数
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
for key, value in kwargs.items():
setattr(self, key, value)
# 这是我们定义的类使用的父类方法 传进去Registerview
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process.
# 请求-响应过程的主要入口点.
"""
for key in initkwargs:
# 遍历as_view()接收的参数
# 省略......
def view(request, *args, **kwargs):
"""准备一个函数视图,将来作为as_view()的返回值,并用于路由匹配"""
# 初始化类视图对象
# 这里cls就是我们传参进去Registerview 传进去进行一轮判断里面带的是get还是post
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
# 将路由中传入的参数,绑定到类视图对象中
self.setup(request, *args, **kwargs)
# 检查类视图是否完整:类视图中必须要有'request' attribute
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
# 调用请求分发的方法(最核心):将请求分发给跟请求方法同名的函数
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
# 最后返回的是闭包函数view
return view
def setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods.
# 初始化所有视图方法共享的属性:将路由中传入的参数,绑定到类视图对象中
"""
self.request = request
self.args = args
self.kwargs = kwargs
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
# 比如:当前客户端发送的请求,请求方法是GET,那么,handler=get
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
# 如果客户端的请求方法不在允许接收的方法列表中,遵从错误处理程序
handler = self.http_method_not_allowed
# 如果请求分发没有问题,那么就去调用该跟请求分发同名的函数
# 如果当前客户端发送的请求,请求方法是GET
# handler(request, *args, **kwargs)等价于get(request, *args, **kwargs)
# 如果handler()调用成功,那么跟请求分发同名的函数就会被调用执行
return handler(request, *args, **kwargs)
def http_method_not_allowed(self, request, *args, **kwargs):
"""错误处理程序:请求方法不匹配时,响应的错误信息"""
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={'status_code': 405, 'request': request}
)
return HttpResponseNotAllowed(self._allowed_methods())
提示:
示例:
class ListModelMixin(object):
"""list扩展类 """
def list(self, request, *args, **kwargs):
pass
class CreateModelMixin(object):
"""create扩展类 """
def create(self, request, *args, **kwargs):
pass
class TestMixinView(CreateModelMixin, ListModelMixin, View):
"""同时继承两个扩展类,复用list和create方法"""
def get(self, request):
self.list(request)
pass
def post(self, request):
self.create(request)
pass
提示:
需求:
http://127.0.0.1:8000/users/login/
**访问用户登录视图class LoginView(View):
"""用户登录类视图
http://127.0.0.1:8000/users/login/
"""
def get(self, request):
"""
处理GET请求,返回登录页面
:param request: 请求对象,包含了请求报文信息
:return: 响应对象,用于构造响应报文,并响应给用户
"""
return http.HttpResponse('假装这是个登录页面')
def post(self, request):
"""
处理POST请求,实现登录逻辑
:param request: 请求对象,包含了请求报文信息
:return: 响应对象,用于构造响应报文,并响应给用户
"""
return http.HttpResponse('假装实现登录逻辑')
说明:
- 在前两节内容中,演示函数视图和类视图时已经实现过了。
1. 注册子路由
from django.urls import re_path
urlpatterns = [
# 函数视图re_path()路由语法:
# re_path(r'^网络地址正则表达式$', 函数视图名),
# 类视图re_path()路由语法:
# re_path(r'^网络地址正则表达式$', 类视图.as_view()),
# re_path 是用于非常规时候 想要使用正则表达式
# re_path 需要严格规定开头结尾,不然容易出现路由屏蔽
# re_path(r"^users/regiser/$", views.Registerview.as_view())
re_path(r"users/login/", views.Loginview.as_view())
# 旧版本的方法 已经被re_path替代
# url(r"users/login/",views.Loginview.as_view())
]
2. postman进行请求测试
- 使用postman分别向
http://127.0.0.1:8000/users/login/
发送GET和POST请求
1. 注册子路由
from django.conf.urls import url
urlpatterns = [
# 函数视图url()路由语法:
# url(r'^网络地址正则表达式$', 函数视图名),
# 类视图url()路由语法:
# url(r'^网络地址正则表达式$', 类视图.as_view()),
# 用户登录:http://127.0.0.1:8000/users/login/
url(r'^users/login/$', views.LoginView.as_view()),
]
2. postman进行请求测试
- 使用postman分别向
http://127.0.0.1:8000/users/login/
发送GET和POST请求
path()
# 函数视图path()路由语法:
# path('网络地址正则表达式', 函数视图名),
# 类视图path()路由语法:
# path('网络地址正则表达式', 类视图.as_view()),
path()
路由语法中,不需要定义正则表达式严格的开头和结尾,因为已经封装好了re_path()、url()
# 函数视图re_path()路由语法:
# re_path(r'^网络地址正则表达式$', 函数视图名),
# 类视图re_path()路由语法:
# re_path(r'^网络地址正则表达式$', 类视图.as_view()),
# 函数视图url()路由语法:
# url(r'^网络地址正则表达式$', 函数视图名),
# 类视图url()路由语法:
# url(r'^网络地址正则表达式$', 类视图.as_view()),
re_path()和url()
路由语法中,必须要定义正则表达式严格的开头和结尾urlpatterns
**列表中的。urlpatterns
列表中以由上至下的顺序查找对应路由规则。include
中包含了,则再进入被包含的urls
中的urlpatterns
列表由上至下进行查询。可能存在的问题:
提示:
re_path()、url()
定义路由时出现。path()
定义路由时,网络地址正则表达式默认就是严格的开头和结尾。例子:
class SayView(View):
"""测试路由屏蔽
http://127.0.0.1:8000/say/
"""
def get(self, request):
return http.HttpResponse('say')
class SayHelloView(View):
"""测试路由屏蔽
http://127.0.0.1:8000/sayhello/
"""
def get(self, request):
return http.HttpResponse('say hello')
# 测试路由屏蔽
# http://127.0.0.1:8000/say/
re_path(r'^say', views.SayView.as_view()),
# http://127.0.0.1:8000/sayhello/
re_path(r'^sayhello', views.SayHelloView.as_view()),
完整的、正确的路由定义方式:
# 测试路由屏蔽
# http://127.0.0.1:8000/say/
re_path(r'^say/$', views.SayView.as_view()),
# # http://127.0.0.1:8000/sayhello/
re_path(r'^sayhello/$', views.SayHelloView.as_view()),
利用HTTP协议向服务器传参有几种途径
?key1=value1&key2=value2
http://www.meiduo.site/list/115/1/?sort=price
中的?sort=price
http://www.meiduo.site/detail/2/
中的/2/
HttpRequest类在request.py文件里有定义
?k1=v1&k2=v2
request.GET
属性获取,并返回QueryDict类型的对象我们创建一个新的子应用,来尝试获取url?后面的字符串数据(query string)。通过GET属性获取QueryDict。
QueryDict是由Django封装的一个数据类型,和Python里Dict类型相似但是不是一种类型,他们被定义在django.http.QueryDict。
它专门用来存储请求中提取的查询字符串参数和请求体参数
QueryDict
的使用:
# 如果键不存在则返回None值,可以设置默认值进行后续处理
query_dict.get('键',默认值)
# 可简写为:
query_dict['键']
代码示例
views文件里
from django.views import View
from django.http import HttpResponse
# Create your views here.
class QSparamview(View):
def get(self,request):
name = request.GET.get('name', '小米')
age = request.GET.get('age', '0')
tel = request.GET['tel']
return HttpResponse("查询字符串参数为:name : %s--- age: %s-- tel: %s" % (name, age, tel))
同样的对这个类视图进行对应的路由配置,然后在页面和postman里分别对对应地址发送GET请求
可以发现因为’tel’属性没有设置默认值,所以空页面报错了。postman里发送了指定值,也同时返了回来。
提取查询字符串参数不区分请求方式,即使客户端进行POST方式的请求,依然可以通过request.GET获取请求中的查询字符串参数。
这里我们没定义POST方式,如果上传会报错。
记住request.GET不是方法,是QueryDict类型的对象。
拓展
前端有时候发来的查询字符串里会有重复的key,虽然我们普通的字典是key唯一,但是QueryDict类型是可以一个键多个值,如果遇到这种方式 我们可以使用request.GET.getlist('属性名',[])
可以获取该键值,且用列表展示。
getlist()
方法是只要是QD类型的对象就能使用,不止GET属性的。
getlist()
不是QD类定义的方法,是继承MultiValueDict
类的方法,这个类的父类是Python的字典类。
# qstring类型的
like = request.GET.getlist("like", [])
return HttpResponse("查询字符串参数为:name : %s--- age: %s \nlike: %s" % (name, age, like))
表单类型数据和JSON字符串类型
,我们应区别对待前端发送的表单类型的请求体数据,可以通过request.POST
属性获取,并返回QueryDict对象。
同理我们用get方式来获取请求体里的表单数据
代码示例
class Formdataview(View):
def post(self,request):
# request.POST 也是一个Query Dict类型的对象
username = request.POST.get("username",'')
password = request.POST.get("password",'')
# 也可以使用诸如 request.POST["属性名"]来获取数据
return HttpResponse("获取表单数据为: \nusername: %s \npassword: %s" % (username, password))
同样的request.POST
也是一个QD对象,所以也可以用诸如request.POST['属性名']
的样式获得请求体里的数据,不过一样是不推荐的,因为如果请求体里没有也一样会报错。
注意
request.POST
只能用来获取post请求方式发送的表单格式数据,如果是非表单格式的,只能用下面的方式获取。
但是我们一般前端发送表单方式一般也只有get和post,所以我们默认如果使用表单就是post方法获取。
request.body
属性获取最原始的请求体数据request.body
获取的是bytes类型(二进制)
的请求体原始数据我们先试用一下用其他请求方式发送表达格式数据,看看获得的数据大致是什么样子的。
# 使用put请求方法发送的表单格式
def put(self, request):
# username = request.body.get("username", 'turkey')
# password = request.body.get("password", 'boom')
print(request.body)
return HttpResponse("获取put请求方式发送的表单格式请求体内容: %s" % request.body.decode("utf-8"))
然后使用postman发送put请求
可以看出我们获得的二进制解码后的内容,杂乱无章,所以我们尝试使用之前的方式去获取的时候即使使用解码也获取不到还报错。对于这种格式我们得自己手动去解包,用正则去匹配到想要的内容。
如果是json格式就有方便取的方式了,我们之前学过一个json.dump()
,可以把(dict)列表格式和对象格式的转化成json格式,相对的也有一个方法可以反过来把json格式的json.loads()
可以把json格式转化成Django可以识别的dict类对象,因为是QueryDict父类,所以也是可以识别。
class JsonParaView(View):
def post(self,request):
json_str = request.body
# 1. request.body 获取的就是最原始的二进制数据
json_dict = json.loads(json_str)
# 2. 因为请求传的是json格式的数据,所以也可以再次转化回来
# json.loads()可以吧json格式数据转化成Dict类型的对象
username = json_dict.get("username", "Kobe")
password = json_dict.get("password", "0")
return HttpResponse("POST方式获取的非表单JSON格式为:\nusername: %s \npassword: %s" %(username,password))
注意
需求:
需求1:
http://127.0.0.1:8000/url_param1/18/
18
需求2:
http://127.0.0.1:8000/url_param2/18500001111/
18500001111
当我们使用path()来做路由的时候,Django里提供了封装一些正则表达式的路径转换器来让我们可以获取其中特定的参数。
我们先来看需求1,只是需要提取其中的18,需要以下代码实现。
# view部分
class Pathparamview(View):
def get(self, request, cost):
'''
:param cost : 路由提取的关键字参数
'''
return HttpResponse("GET方式中用路由转换器获取的参数 cost: %s" % cost)
# urls部分
path("request_response/pathdata//" , views.Pathparamview.as_view()),
其中int:xxx就是Django里封装好的路径转换器,通过这个转换器我们能获取到想要的参数。
路径转换器
下面的路径转换器在默认情况下是有效的:
str
- 匹配除了 '/'
之外的非空字符串。如果表达式内不包含转换器,则会默认匹配字符串。int
- 匹配0或任何正整数。返回一个 int 。slug
- 匹配任意由 ASCII 字母或数字以及连字符和下划线组成的短标签。比如,building-your-1st-django-site
。uuid
- 匹配一个格式化的 UUID 。为了防止多个 URL 映射到同一个页面,必须包含破折号并且字符都为小写。比如,075194d3-6885-417e-a8a8-6c931e272f00
。返回一个 UUID
实例。path
- 匹配非空字段,包括路径分隔符 '/'
。它允许你匹配完整的 URL 路径而不是像 str
那样只匹配 URL 的一部分。也就是说只要满足需求的话,我们只需要使用默认好的路径转换器就能提取到想要的参数,但是对于需求2明显是不可行的,因为默认的提取里没法判定一串数字是否是手机号码。
这时候我们就需要自定路径转换器了。
converters.py
文件,用于自定义路由转换器转换器是一个类,包含如下内容:
regex
类属性。to_python(self, value)
方法,它处理匹配的字符串转换为应该传递到函数的类型。如果没有转换为给定的值,它应该会引发 ValueError
。ValueError
被解释为不匹配,因此向用户发送404响应。to_url(self, value)
,处理将 Python 类型转换为 URL 中要使用的字符串。我们创建一个PhoneNumConverter
类来定义一个提取手机号码的路径转换器。
# 自定义一个匹配手机号码的路由转换器类
class PhoneNumConverter:
# 匹配手机号码的正则表达式
# 固定的类属性名 不能更改
regex = '1[3-9]\d{9}'
def to_python(self,value):
# 将匹配字符处理传递到函数内部里可以使用的类型
return int(value)
def to_url(self,value):
# 将匹配结果用于反向解析传值
return str(value)
然后得去主路由projetc/urls.py
那边注册我们刚做完的转换器
# 在主路由里导入注册路由转换器模块
from django.urls import register_converter
from . import converters
# 在主路由里用这个方法来注册自定义的路径转换器 register_converter(class,"xxx")
register_converter(converters.PhoneNumConverter, "mobile")
最后再去子路由那边添加我们来测试用的路由参数方法
# views里的方法
class Pathparam2view(View):
def get(self, reequest, phone):
'''
:param phone : 自定义的路由转换器提取出来的关键字传参
'''
return HttpResponse("GET方式中用自定义路由转换器获取的参数 phonr: %s" % phone)
# 子路由表里的参数
path("request_response/pathdata2//" , views.Pathparam2view.as_view()),
然后用postman测试,发现只有输入正确的手机号码才会正确匹配,否则会返回错误404的提示。
可以发现如果没有默认转换器去自己定义的时候特别麻烦,我们可以使用re_path()来实现简单的路径参数提取。
view类视图里创建一个
class RepathView(View):
def get(self,request,phone):
'''
:param phone : re_path 里由正则取出来的别名传参
'''
return HttpResponse("GET方式中用re_path()获取的参数 phone_num: %s" % phone)
子路由里
re_path(r"^request_response/repathdata/(?P1[2-9]\d{9})/$" , views.RepathView.as_view()),
注意
可以通过request.META
属性获取请求头headers中的数据,request.META
为字典类型。
常见的请求头如:
CONTENT_LENGTH
– The length of the request body (as a string).CONTENT_TYPE
– The MIME type of the request body.HTTP_ACCEPT
– Acceptable content types for the response.HTTP_ACCEPT_ENCODING
– Acceptable encodings for the response.HTTP_ACCEPT_LANGUAGE
– Acceptable languages for the response.HTTP_HOST
– The HTTP Host header sent by the client.HTTP_REFERER
– The referring page, if any.HTTP_USER_AGENT
– The client’s user-agent string.QUERY_STRING
– The query string, as a single (unparsed) string.REMOTE_ADDR
– The IP address of the client.REMOTE_HOST
– The hostname of the client.REMOTE_USER
– The user authenticated by the Web server, if any.REQUEST_METHOD
– A string such as "GET"
or "POST"
.SERVER_NAME
– The hostname of the server.SERVER_PORT
– The port of the server (as a string).# view类视图里
class HeaderView(View):
def get(self,request):
'''
:param : request
'''
print(request.META)
content_type = request.META.get("CONTENT_TYPE", "")
return HttpResponse("反应头里获取的信息 CONTENT_TYPE: %s" %content_type)
# urls子路由里
path("request_response/headerdata/", views.HeaderView.as_view()),
shell里通过print输出的信息可以看出
{'PATH': '/Users/toki/.virtualenv/env01/bin:/Users/toki/.nvm/versions/node/v14.15.0/bin:/Library/Frameworks/Python.framework/Versions/3.6/bin:/Library/Frameworks/Python.framework/Versions/3.8/bin:/Library/Frameworks/Python.framework/Versions/3.9/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/VMware Fusion.app/Contents/Public:/Library/Apple/usr/bin', 'WORKON_HOME': '/Users/toki/.virtualenv', 'COMMAND_MODE': 'unix2003', 'PS1': '(env01) ', 'VIRTUALENVWRAPPER_SCRIPT': '/Library/Frameworks/Python.framework/Versions/3.8/bin/virtualenvwrapper.sh', 'NVM_INC': '/Users/toki/.nvm/versions/node/v14.15.0/include/node', 'VERSIONER_PYTHON_VERSION': '2.7', 'VIRTUALENVWRAPPER_WORKON_CD': '1', 'LOGNAME': 'toki', 'XPC_SERVICE_NAME': 'application.com.jetbrains.pycharm.754912.8010409', 'PWD': '/Volumes/Data/Project/GitLib/test001/my_project', 'PYCHARM_HOSTED': '1', 'PYCHARM_DISPLAY_PORT': '63342', '__CFBundleIdentifier': 'com.jetbrains.pycharm', 'PYTHONPATH': '/Volumes/Data/Project/GitLib/test001/my_project:/Applications/PyCharm.app/Contents/plugins/python/helpers/pycharm_matplotlib_backend:/Applications/PyCharm.app/Contents/plugins/python/helpers/pycharm_display', 'NVM_CD_FLAGS': '-q', 'NVM_DIR': '/Users/toki/.nvm', 'SHELL': '/bin/zsh', 'PAGER': 'less', 'LSCOLORS': 'Gxfxcxdxbxegedabagacad', 'PYTHONIOENCODING': 'UTF-8', 'SECURITYSESSIONID': '186a6', 'OLDPWD': '/', 'USER': 'toki', 'VIRTUALENVWRAPPER_HOOK_DIR': '/Users/toki/.virtualenv', 'ZSH': '/Users/toki/.oh-my-zsh', 'TMPDIR': '/var/folders/pz/nf83s_0n08qb8_yg8qng2p8c0000gn/T/', 'LaunchInstanceID': 'C948A116-9EF1-4471-A3FB-581D306C9711', 'SSH_AUTH_SOCK': '/private/tmp/com.apple.launchd.5AyOdvFuxa/Listeners', 'VIRTUAL_ENV': '/Users/toki/.virtualenv/env01', 'XPC_FLAGS': '0x0', 'PYTHONUNBUFFERED': '1', 'VIRTUALENVWRAPPER_PROJECT_FILENAME': '.project', '__CF_USER_TEXT_ENCODING': '0x1F5:0x19:0x34', 'LESS': '-R', 'LC_CTYPE': 'zh_CN.UTF-8', 'NVM_BIN': '/Users/toki/.nvm/versions/node/v14.15.0/bin', 'HOME': '/Users/toki', 'DJANGO_SETTINGS_MODULE': 'my_project.settings', 'TZ': 'UTC', 'RUN_MAIN': 'true', 'SERVER_NAME': '1.0.0.127.in-addr.arpa', 'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_PORT': '8000', 'REMOTE_HOST': '', 'CONTENT_LENGTH': '', 'SCRIPT_NAME': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 'SERVER_SOFTWARE': 'WSGIServer/0.2', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/request_response/headerdata/', 'QUERY_STRING': '', 'REMOTE_ADDR': '127.0.0.1', 'CONTENT_TYPE': 'text/plain', 'HTTP_USER_AGENT': 'PostmanRuntime/7.26.8', 'HTTP_ACCEPT': '*/*', 'HTTP_POSTMAN_TOKEN': '0c74072c-ddd3-456e-b586-5c5d1904be7d', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_REFERER': 'http://127.0.0.1:8000/request_response/headerdata', 'HTTP_HOST': '127.0.0.1:8000', 'wsgi.input': <django.core.handlers.wsgi.LimitedStream object at 0x7ff5fcb4ed30>, 'wsgi.errors': <_io.TextIOWrapper name='' mode='w' encoding='utf-8'>, 'wsgi.version': (1, 0), 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.multithread': True, 'wsgi.multiprocess': False, 'wsgi.file_wrapper': <class 'wsgiref.util.FileWrapper'>}
request.META
获取的也是一串字典类型的
默认情况下请求头我们无法更改头类,如果我们尝试给请求头添加参数,会发现会自动被改名了,一般会改成HTTP_开头的。
注意
请求头里的数据也是一样无论GET或者POST方式都能获取
method
:一个字符串,表示请求使用的HTTP方法,常用值包括:‘GET’、‘POST’。
user = request.user
request.user
获取到的不一定是当前登录用户对象request.user
获取到的才是当前登录用户对象request.user
获取到的会是一个AnonymousUser对象(匿名用户,没有任何用户信息,没有使用价值)。request.user
request.user
需要搭配用户访问的限制来使用。request.user
。HttpResponse()
:响应多种数据类型JsonResponse()
:响应JSONredirect()
:重定向render()
:渲染并响应HTML模板示例:
# 测试HttpResponse:http://127.0.0.1:8000/response1/
path('response1/', views.Response1View.as_view()),
class Response1View(View):
"""测试HttpResponse
http://127.0.0.1:8000/response1/
"""
def get(self, request):
# 使用HttpResponse构造响应数据
# return http.HttpResponse(content='itcast python', status=200)
# 可简写
# return http.HttpResponse('itcast python')
# 另外一种写法
response = http.HttpResponse('itcast python')
return response
补充:HttpResponse子类
Django提供了一系列HttpResponse的子类,可以快速设置状态码
HttpResponseRedirect
默认响应状态码为 301HttpResponsePermanentRedirect
默认响应状态码为 302HttpResponseNotModified
默认响应状态码为 304HttpResponseBadRequest
默认响应状态码为 400HttpResponseNotFound
默认响应状态码为 404HttpResponseForbidden
默认响应状态码为 403HttpResponseNotAllowed
默认响应状态码为 405HttpResponseGone
默认响应状态码为 410HttpResponseServerError
默认响应状态码为 500在开发功能时,如果前端需要JSON数据,那么后端就需要构造并响应JSON数据
而Django提供了**JsonResponse
**来构造并响应JSON数据
JsonResponse
作用:
示例:
# 测试JSONResponse:http://127.0.0.1:8000/json_resp/
path('json_resp/', views.JSONResponseView.as_view()),
class JSONResponseView(View):
"""测试JSONResponse
http://127.0.0.1:8000/json_resp/
"""
def get(self, request):
# 准备要响应的数据
dict_data = {
'city': 'beijing',
'subject': 'python'
}
# 使用JSONResponse构造并响应JSON数据
return http.JsonResponse(dict_data)
需求:
LoginRedirectView
LoginRedirectView
时,如果其中的登录逻辑处理完成,我们将用户重定向到首页示例:
# 测试重定向
path('login_redirect/', views.LoginRedirectView.as_view()),
path('index/', views.IndexView.as_view()),
from django.shortcuts import render, redirect
class IndexView(View):
# 测试重定向到一个页面
def get(self, request):
return HttpResponse("要导向的网页")
class LoginRedirectView(View):
# 测试假装一个登陆成功后发送一个重定向到新路径
def post(self, request):
# 假装正在处理登录逻辑
# 假装登录逻辑处理完成
# ......
# 将用户通过重定向引导到首页
return redirect('/request_response/index/')
解决方案:
示例:
总路由中,给子应用的总路由起别名
urlpatterns = [
# 请求和响应
# path('', include(('子路由', '子应用名字'), namespace='总路由别名,可以随便命名')),
path('', include(('request_response.urls', 'request_response'), namespace='request_response')),
]
子路由中,给子应用的子路由起别名
# 测试重定向
path('login_redirect/', views.LoginRedirectView.as_view()),
path('index/', views.IndexView.as_view(), name='index'),
视图中,使用路由的别名,动态的解析出该路由中的真实地址
from django.shortcuts import render, redirect, reverse
class IndexView(View):
"""测试重定向
http://127.0.0.1:8000/index/
"""
def get(self, request):
return http.HttpResponse('假装这是个网站首页')
class LoginRedirectView(View):
"""测试重定向
http://127.0.0.1:8000/login_redirect/
"""
def post(self, request):
# 假装正在处理登录逻辑
# 假装登录逻辑处理完成
# ......
# 将用户通过重定向引导到首页
# return redirect('/index/')
# ret_url = reverse('总路由别名:子路由别名')
ret_url = reverse('request_response:index')
return redirect(ret_url)
概念:
使用场景:
设计思想:
默认的中间件
# 中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
2.1 初始化方法:
启动Django程序,初始化中间件时,自动调用一次,用于确定是否启用当前中间件
def __init__(self, get_response=None):
pass
2.2 处理请求前的方法:(重要)
在处理每个请求前,自动调用,返回None或HttpResponse对象
def process_request(self, request):
pass
2.3 处理视图前的方法:(重要)
在处理每个视图前,自动调用,返回None或HttpResponse对象
def process_view(self, request, view_func, view_args, view_kwargs):
pass
2.4 处理模板响应前的方法:
在处理每个模板响应前,自动调用,返回实现了render方法的响应对象
def process_template_response(self, request, response):
pass
2.5 处理响应后的方法:(重要)
在每个响应返回给客户端之前,自动调用,返回HttpResponse对象
def process_response(self, request, response):
pass
2.6 异常处理:
当视图抛出异常时,自动调用,返回一个HttpResponse对象
def process_exception(self, request,exception):
pass
middlewares.py
文件来自定义中间件# 导入中间件的父类
from django.utils.deprecation import MiddlewareMixin
class TestMiddleware1(MiddlewareMixin):
"""自定义中间件"""
def process_request(self, request):
"""处理请求前自动调用"""
print('process_request1 被调用')
def process_view(self, request, view_func, view_args, view_kwargs):
# 处理视图前自动调用
print('process_view1 被调用')
def process_response(self, request, response):
"""在每个响应返回给客户端之前自动调用"""
print('process_response1 被调用')
return response
注册自定义的中间件
# 中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'middlewares.TestMiddleware1', # 注册自定义的中间件1
]
准备两个自定义的中间件
from django.utils.deprecation import MiddlewareMixin
class TestMiddleware1(MiddlewareMixin):
"""自定义中间件"""
def process_request(self, request):
"""处理请求前自动调用"""
print('process_request1 被调用')
def process_view(self, request, view_func, view_args, view_kwargs):
# 处理视图前自动调用
print('process_view1 被调用')
def process_response(self, request, response):
"""在每个响应返回给客户端之前自动调用"""
print('process_response1 被调用')
return response
class TestMiddleware2(MiddlewareMixin):
"""自定义中间件"""
def process_request(self, request):
"""处理请求前自动调用"""
print('process_request2 被调用')
def process_view(self, request, view_func, view_args, view_kwargs):
# 处理视图前自动调用
print('process_view2 被调用')
def process_response(self, request, response):
"""在每个响应返回给客户端之前自动调用"""
print('process_response2 被调用')
return response
注册多个自定义的中间件
# 中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'middlewares.TestMiddleware1', # 注册自定义的中间件1
'middlewares.TestMiddleware2', # 注册自定义中的间件2
]
浏览多个中间件的调用过程
重要提示:中间件执行顺序
安装mysqlclient依赖
pip install mysqlclient
如果也失败就先安装libmysqlclient-dev依赖
sudo apt-get install libmysqlclient-dev
拓展
早期Django代码使用的是Python2的时候的模块MySQLdb来连接mysql数据库,在版本迭代后已经无力再去修改里面的内容,且现在的Python3根本不支持mysqldb,而是使用pymsql模块连接,现在Django里需要你安装这俩个依赖,来使得在Django里输出的SQL语句能转换到pymsql里识别的语句。
ORM框架介绍
提示:
ORM框架作用:
子应用/models.py
文件中django.db.models
中booktest
,并在其models.py
文件中定义模型类from django.db import models
# Create your models here.
class BookInfo(models.Model):
"""图书信息:演示一对多,一方"""
btitle = models.CharField(max_length=20,verbose_name='图书名')
bpub_date = models.DateField(verbose_name='发布日期')
bread = models.IntegerField(default=0,verbose_name='阅读量')
bcomment = models.IntegerField(default=0,verbose_name='评论量')
is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')
class Meta:
"""模型类的元类:用于修改、配置模型类对应的数据表"""
db_table = 'tb_books'
# 自定义数据库表名
def __str__(self):
"""定义每个数据对象的显示信息"""
return self.btitle
# 输出该模型数据对象时,只输出书名
class HeroInfo(models.Model):
""" 英雄信息"""
# django 里没有mysql里对应的枚举类型,所以得自己来定义性别范围的取值范围
GENDER_CHOICES = (
(0,'female'),
(1,'male')
)
hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='英雄属于的图书')
hname = models.CharField(max_length=20, verbose_name='英雄名')
hgender = models.SmallIntegerField(choices=GENDER_CHOICES,default=0,verbose_name='性别')
hcomment = models.CharField(max_length=200, null=True,verbose_name='描述信息')
is_delete = models.BooleanField(default=False,verbose_name='逻辑删除')
# model的元类 元类是Python里较难理解的一个概念,它能做到修改 截断 返回修改后的类
# 我们创建的类也是一个对象 是一个拥有能够复制属性初始化属性的对象的对象
class Meta:
db_table = 'tb_heros'
# 打印英雄对象的时候返回英雄名
def __str__(self):
return self.hname
id
,可以使用pk
代替,pk全拼为primary key不能是python的保留关键字
不允许使用连续的下划线,这是由Django的查询方式决定的
定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:
属性 = models.字段类型(选项)
表名取别名是用于在后端显示的时候会显示出来
db_table
自定义数据库表名类型 | 说明 |
---|---|
AutoField | 自动增长的IntegerField,通常不用指定,不指定时Django会自动创建属性名为id的自动增长属性 |
BooleanField | 布尔字段,值为True或False |
NullBooleanField | 支持Null、True、False三种值 |
CharField | 字符串,参数max_length表示最大字符个数 |
TextField | 大文本字段,一般超过4000个字符时使用 |
IntegerField | 整数 |
DecimalField | 十进制浮点数, 参数max_digits表示总位数, 参数decimal_places表示小数位数 |
FloatField | 浮点数 |
DateField | 日期, 参数auto_now表示每次保存对象时,自动设置该字段为当前时间,用于"最后一次修改"的时间戳,它总是使用当前日期,默认为False; 参数auto_now_add表示当对象第一次被创建时自动设置当前时间,用于创建的时间戳,它总是使用当前日期,默认为False; 参数auto_now_add和auto_now是相互排斥的,组合将会发生错误 |
TimeField | 时间,参数同DateField |
DateTimeField | 日期时间,参数同DateField |
FileField | 上传文件字段 |
ImageField | 继承于FileField,对上传的内容进行校验,确保是有效的图片 |
选项 | 说明 |
---|---|
null | 如果为True,表示允许为空,默认值是False |
blank | 如果为True,表示允许传递空串,空字符串 |
db_column | 字段的名称,如果未指定,则使用属性的名称 |
db_index | 若值为True, 则在表中会为此字段创建索引,默认值是False |
default | 默认 |
primary_key | 若为True,则该字段会成为模型的主键字段,默认值是False,一般作为AutoField的选项使用 |
unique | 如果为True, 这个字段在表中必须有唯一值,默认值是False |
注意
null是数据库的范围,而blank是用于验证。如果一个字段的 blank=True ,Django 在进行表单数据验证时,会允许该字段是空值。如果字段的 blank=False ,该字段就是必填的。
False
的,如果设置为True
的时候,django
将会映射到数据表指定是否为空False
的时候,如果没给这个字段传递任何值的时候,django
也会使用一个空字符串(''
)存储进去True
的时候,django
会产生两种空值的情形(null
和空字符串)django
建议使用blank=True
外键设置格式为
xxx = models.ForeignKey(关联的模型类名,on_delete=以下可选常量)
在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:
设置外键时,如果不指定,一般主表会多一个隐藏的字段名 从表小写_set
可以用来查询主表单一对象所对应从表的多个对象,设置了related_name后则该字段名会替代了自带的隐藏字段。
ManyToManyField
假设有2张表,房屋表和房屋设施表,一个房屋有多种设备,反过来,一个设备也是不止一个房屋有,此时二者的对象就是多对多,我们会设置ManyToManyField
字段,此时django会自动帮你建立一张隐藏中间表,来记录二者的对应关系。
我们简单使用的话,假如在House模型类里建立了facility多对多字段,此时house对象可以直接通过house.facility.all()
取得所有该房屋对象所拥有的所有房屋设施对象。相反的,facility只能通过隐藏的facility.house_set.all()
来取得拥有该设施的所有房屋对象。
python manage.py makemigrations
注意
如果显示没有添加任何修改 请去看setting.py
是否去注册了子应用
python manage.py migrate
注意
谨记不要Django里建表后又手动去更改表结构,这样子django里的历史记录不正确,后面再度使用django更改的时候容易出错
insert into tb_books(btitle,bpub_date,bread,bcomment,is_delete) values
('射雕英雄传','1980-5-1',12,34,0),
('天龙八部','1986-7-24',36,40,0),
('笑傲江湖','1995-12-24',20,80,0),
('雪山飞狐','1987-11-11',58,24,0);
insert into tb_heros(hname,hgender,hbook_id,hcomment,is_delete) values
('郭靖',1,1,'降龙十八掌',0),
('黄蓉',0,1,'打狗棍法',0),
('黄药师',1,1,'弹指神通',0),
('欧阳锋',1,1,'蛤蟆功',0),
('梅超风',0,1,'九阴白骨爪',0),
('乔峰',1,2,'降龙十八掌',0),
('段誉',1,2,'六脉神剑',0),
('虚竹',1,2,'天山六阳掌',0),
('王语嫣',0,2,'神仙姐姐',0),
('令狐冲',1,3,'独孤九剑',0),
('任盈盈',0,3,'弹琴',0),
('岳不群',1,3,'华山剑法',0),
('东方不败',0,3,'葵花宝典',0),
('胡斐',1,4,'胡家刀法',0),
('苗若兰',0,4,'黄衣',0),
('程灵素',0,4,'医术',0),
('袁紫衣',0,4,'六合拳',0);
Django的manage工具提供了shell命令,帮助我们配置好当前工程的运行环境(如连接好数据库等),以便可以直接在终端中执行测试python语句。
如果有需要,通过如下命令进入shell
python manage.py shell
导入两个模型类,以便后续使用
from booktest.models import BookInfo, HeroInfo
拓展 日志文件
在mysql配置文件里 有2项是和日志相关的,分别设置了日志文件目录,和日志文件是否打开。
默认是关闭的,解除注释后打开重启mysql服务器。
然后使用tail -f 日志文件路径 可以实时查看日志。
如果权限不足就加sudo。
增加数据有2种方法
book = BookInfo()
book.btitle = '西游记'
book.bpub_date = '2020-05-18'
book.bread = 20
book.bcomment = 30
book.save()
类名.objects.create()添加
>>> BookInfo.objects.create(
... btitle='三国演义',
... bpub_date='2020-05-20',
... bread = 300,
... bcomment = 90
... )
<BookInfo: 三国演义>
>>> hero = HeroInfo.objects.get(hname='猪八戒')
>>> hero.hanme = '猪悟能'
>>> hero.save()
>>> HeroInfo.objects.filter(hname='沙悟净').update(hname='沙僧')
1
如果找不到对应表数据则会显示0
>>> hero=HeroInfo.objects.get(id=13)
>>> hero.delete()
(1, {'booktest.HeroInfo': 1})
>>> HeroInfo.objects.filter(id=14).delete()
(1, {'booktest.HeroInfo': 1})
Django里的查会把查询到的对象输出(打印)
BookInfo.objects.all()
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>, <BookInfo: 三国演义>]>
>>> HeroInfo.objects.filter(hbook_id=1).all()
<QuerySet [<HeroInfo: 郭靖>, <HeroInfo: 黄蓉>, <HeroInfo: 黄药师>, <HeroInfo: 欧阳锋>, <HeroInfo: 梅超风>]>
查询所有符合内容的对象(表数据内容)
查询符合结果内容的数量,也就是等于all()里的对象数量
>>> HeroInfo.objects.count()
17
>>> HeroInfo.objects.filter(hbook_id=1).count()
5
取得单个对象,取不到的时候会抛出模型类.DoseNotExist
异常
筛选n’n’n’n’n’n以外的对象
实现SQL中的where功能,包括
过滤条件的表达语法如下:
属性名称__比较运算符=值
# 属性名称和比较运算符间使用两个下划线,所以属性名不能包括多个下划线
注意 小心混淆get和filter
>>> BookInfo.objects.filter(id__exact=1)
<QuerySet [<BookInfo: 射雕英雄传>]>
>>> BookInfo.objects.get(id__exact=1)
<BookInfo: 射雕英雄传>
BookInfo.objects.filter(id__exact=1)
可简写为:
BookInfo.objects.filter(id=1)
>>> BookInfo.objects.filter(btitle__contains='传')
<QuerySet [<BookInfo: 射雕英雄传>]>
BookInfo.objects.filter(btitle__endswith='部')
注意
以上条件查询都区分大小写,如果想不区分必须在各个区分条件之前加个i 比如icontains
>>> BookInfo.objects.filter(btitle__isnull=True)
<QuerySet []>
>>> BookInfo.objects.filter(btitle__isnull=False)
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>, <BookInfo: 三国演义>]>
in
是否在列表[]内
>>> BookInfo.objects.filter(id__in=[1,5,8])
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 西游记>]>
range
是否在某个范围内
>>> BookInfo.objects.filter(id__range=(1,3))
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>]>
>>> BookInfo.objects.filter(id__gt=3)
<QuerySet [<BookInfo: 雪山飞狐>, <BookInfo: 西游记>, <BookInfo: 三国演义>]>
>>> BookInfo.objects.filter(id__gte=3)
<QuerySet [<BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>, <BookInfo: 西游记>, <BookInfo: 三国演义>]>
year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。
例:查询1980年发表的图书。
BookInfo.objects.filter(bpub_date__year=1980)
例:查询1980年1月1日后发表的图书。
BookInfo.objects.filter(bpub_date__gt='1990-1-1')
第二种使用date类
首先要导入datetime
from datetime import date
BookInfo.objects.filter(bpub_date__gt=date(1990,1,1))
之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢? 答:使用F对象,被定义在django.db.models中。要先导包。
语法如下:
F(属性名)
例:查询阅读量大于等于评论量的图书。
from django.db.models import F
BookInfo.objects.filter(bread__gte=F('bcomment'))
可以在F对象上使用算数运算。
例:查询阅读量大于2倍评论量的图书。
BookInfo.objects.filter(bread__gt=F('bcomment') * 2)
多个过滤器逐个调用表示逻辑与关系,同sql语句中where部分的and关键字。
例:查询阅读量大于20,并且编号小于3的图书。
BookInfo.objects.filter(bread__gt=20,id__lt=3)
或
BookInfo.objects.filter(bread__gt=20).filter(id__lt=3)
如果需要实现逻辑或or的查询,需要使用Q()对象结合|运算符,Q对象被义在django.db.models中。
语法如下:
Q(属性名__运算符=值)
例:查询阅读量大于20的图书,改写为Q对象如下。
from django.db.models import Q
BookInfo.objects.filter(Q(bread__gt=20))
Q对象可以使用&、|连接,&表示逻辑与,|表示逻辑或。
例:查询阅读量大于20,或编号小于3的图书,只能使用Q对象实现
BookInfo.objects.filter(Q(bread__gt=20) | Q(pk__lt=3))
Q对象前可以使用~操作符,表示非not。
例:查询编号不等于3的图书。
BookInfo.objects.filter(~Q(pk=3))
使用aggregate()过滤器调用聚合函数。聚合函数包括:Avg 平均,Count 数量,Max 最大,Min 最小,Sum 求和,被定义在django.db.models中。
例:查询图书的总阅读量。
from django.db.models import Sum
BookInfo.objects.aggregate(Sum('bread'))
注意aggregate的返回值是一个字典类型,格式如下:
{'属性名__聚合类小写':值}
如:{'bread__sum':3}
使用count时一般不使用aggregate()过滤器。
例:查询图书总数。
BookInfo.objects.count()
注意count函数的返回值是一个数字。
使用order_by对结果进行排序
BookInfo.objects.all().order_by('bread') # 升序
BookInfo.objects.all().order_by('-bread') # 降序
由一到多的访问语法:
一对应的模型类对象.多对应的模型类名小写_set
例:
b = BookInfo.objects.get(id=1)
b.heroinfo_set.all()
由多到一的访问语法:
多对应的模型类对象.多对应的模型类中的关系类属性名
例:
h = HeroInfo.objects.get(id=1)
h.hbook
访问一对应的模型类关联对象的id语法:
多对应的模型类对象.关联类属性_id
例:
h = HeroInfo.objects.get(id=1)
h.hbook_id
拓展
class BookInfo(models.Model):
"""图书信息模型类"""
btitle = models.CharField(max_length=20, verbose_name='名称')
class HeroInfo(models.Model):
hname = models.CharField(max_length=20, verbose_name='名称')
# 外键
hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书')
hero.hbook.id
hero.hbook_id
hero.hbook.id
:不安全,如果hbook为空,会报错,因为空对象不能读取任何属性hero.hbook_id
:安全,如果hbook_id为空,不会报错,获取的是空值方式二
读取外键Django的ORM中存在查询集的概念。
查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。
当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表):
对查询集可以再次调用过滤器进行过滤,如
BookInfo.objects.filter(bread__gt=30).order_by('bpub_date')
也就意味着查询集可以含有零个、一个或多个过滤器。过滤器基于所给的参数限制查询的结果。
从SQL的角度讲,查询集与select语句等价,过滤器像where、limit、order by子句。
特点
判断某一个查询集中是否有数据:
创建查询集不会访问数据库,直到调用数据时,才会访问数据库,调用数据的情况包括迭代、序列化、与if合用
例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集qs
qs = BookInfo.objects.all()
继续执行遍历迭代操作后,才真正的进行了数据库的查询
for book in qs:
print(book.btitle)
使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。
拓展