Django基础02 视图和模型

2. Django的V和M

2.1 视图(views)

  • 视图是Django程序中处理后端业务逻辑的地方。
  • Django的视图是定义在子应用的views.py中的。
  • Django的视图分为 函数视图类视图 两种。

2.1.1 函数视图

1. 定义函数视图

函数视图定义方式:

"""
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('这里假装返回注册页面')

2. 访问函数视图

提示:

  • 我们定义好的函数视图,需要用户能够访问到。
  • 用户如何访问函数视图?
    • 通过网络地址向Django程序发请求,即可访问到函数视图

问题:

  • 如何保证用户发送的请求,能够访问到对应的函数视图?

解决:

  • 路由:使用路由匹配请求地址,每匹配成功一个就执行对应的函数视图逻辑
  • 定义路由的方法:path()、re_path()、url()

需求:

  • 用户通过网络地址**http://127.0.0.1:8000/users/register/**访问用户注册视图

3. 访问函数视图:需求实现 --> path()

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/

Django基础02 视图和模型_第1张图片

拓展

r转义字符

​ 一般会在路由地址之前添加一个r来防止有\造成地址出错

path()和re_path()

​ django里一般情况下使用path已经能解决大部分路由路径需求,虽然不能使用正则表达式,但是可以使用路径转换器来达成和正则差不多的效果且代码可读性提高。

​ 但是依旧有需要使用正则的时候,此时我们就使用re_path()来匹配路由路径

4. 路由匹配函数视图逻辑

Django基础02 视图和模型_第2张图片

需求:

  • 用户向地址**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('这里假装实现注册逻辑')

函数视图问题说明:

  • 当遇到视图对应的同一个路径,提供了多种不同HTTP请求方式的支持时,便需要在一个函数中编写不同的业务逻辑,代码可读性与复用性都很差。

解决方案:

  • 类视图

2.1.2 类视图

类视图定义方式:

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('这里假装实现注册逻辑')

类视图的好处:

  • 代码可读性好
  • 类视图相对于函数视图有更高的复用性, 如果其他地方需要用到某个类视图的某个特定逻辑,直接继承该类视图即可。

1. 访问类视图

说明:

  • 类视图的访问和函数视图的访问是一模一样的。
  • 类视图的访问也是使用路由匹配请求地址,每匹配成功一个就执行对应的类视图逻辑

需求:

  • 用户向地址**http://127.0.0.1:8000/users/register/**发送GET请求,用来获取注册页面。
  • 用户向地址**http://127.0.0.1:8000/users/register/**发送POST请求,用来实现注册逻辑。

2. 访问类视图:需求实现 --> path()

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. 启动运行测试

  • 2.1 注释CSRF中间件
    • Django默认开启了CSRF防护,会对非GET请求(POST, PUT, DELETE)进行CSRF防护验证,在测试时可以关闭CSRF防护机制
    • 关闭CSRF防护机制是在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',
]
  • 2.2 重新启动Django程序
python manage.py runserver
  • 2.3 使用postman进行请求测试: http://127.0.0.1:8000/users/register/

Django基础02 视图和模型_第3张图片


Django基础02 视图和模型_第4张图片

3. 路由匹配类视图逻辑

Django基础02 视图和模型_第5张图片

4. as_view()底层原理(仅做了解)

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())

5. 类视图添加扩展类

提示:

  • 使用面向对象多继承的特性,可以给类视图定义扩展类。
  • 在扩展类中,可以定义想要向类视图补充的方法。
  • 类视图继承这些扩展类作为父类,便可实现代码复用。
    • 比如我们以前在数据库定义过的打印查询内容方法,我们可以将其做成一个扩展类,以后视图里就能使用这个扩展类的方法。

示例:

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

2.1.3 路由

提示:

  • 路由可以保证用户发送的请求,能够访问到对应的视图
  • 使用路由匹配请求地址,每匹配成功一个就执行对应的函数视图逻辑
  • 定义路由的方法:path()、re_path()、url()
    • Django==1.x版本:url()
    • Django==2.x版本:path()、re_path()
  • 说明:
    • 为了在版本迭代中,保留旧版本的路由系统,url()在新版中依然可用。
    • 并新增了一个url()的替代方案re_path(),所以url()几乎跟re_path()一样。

需求:

  • 用户通过网络地址**http://127.0.0.1:8000/users/login/**访问用户登录视图
  • 我们后续演示路由,都会选择使用类视图。

1. 定义用户登录类视图

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('假装实现登录逻辑')

2. path()定义路由

说明:

  • 在前两节内容中,演示函数视图和类视图时已经实现过了。

3. re_path()定义路由

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请求

3. url()定义路由

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请求

4. 路由方法对比

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()路由语法中,必须要定义正则表达式严格的开头和结尾

5. 路由解析顺序

  • Django的总路由和子路由都是定义在**urlpatterns**列表中的。
  • Django在接收到一个请求时,从总路由文件中的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()),

Django基础02 视图和模型_第6张图片

Django基础02 视图和模型_第7张图片

完整的、正确的路由定义方式:

# 测试路由屏蔽
# 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()),

Django基础02 视图和模型_第8张图片

Django基础02 视图和模型_第9张图片

2.1.4 请求对象 HttpRequest

  • 用户发送请求时携带的参数后端需要使用,而不同的发送参数的方式对应了不同的提取参数的方式
  • 所以要学会如何提取参数,我们就需要先了解前端传参数有哪些方式

利用HTTP协议向服务器传参有几种途径

  • 查询字符串数据(query string):
    • 形如:?key1=value1&key2=value2
    • 比如:http://www.meiduo.site/list/115/1/?sort=price中的?sort=price
  • 请求体数据(body):
    • 比如:表单数据、json、…
  • URL路径中的特定部分数据:
    • 比如:http://www.meiduo.site/detail/2/中的/2/
    • 请求地址中的该部分数据,可以在路由中使用正则表达式提取出来
  • 请求头数据:
    • HTTP请求报文中的请求头数据(header)

HttpRequest类在request.py文件里有定义

Django基础02 视图和模型_第10张图片

1. 提取字符串数据

  • 获取请求路径中的查询字符串参数,形如:?k1=v1&k2=v2
  • 可以通过request.GET属性获取,并返回QueryDict类型的对象

我们创建一个新的子应用,来尝试获取url?后面的字符串数据(query string)。通过GET属性获取QueryDict。

QueryDict是由Django封装的一个数据类型,和Python里Dict类型相似但是不是一种类型,他们被定义在django.http.QueryDict。

  • 它专门用来存储请求中提取的查询字符串参数和请求体参数

    • 即,HttpRequest对象中的属性GET、POST都是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里发送了指定值,也同时返了回来。Django基础02 视图和模型_第11张图片

提取查询字符串参数不区分请求方式,即使客户端进行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))

2. 提取请求体数据

  • 可以发送请求体数据的请求方式有:POSTPUTPATCHDELETE
  • 请求体数据格式不固定,常见的有:表单类型数据和JSON字符串类型,我们应区别对待
2.1 表单类型请求体数据(Form Data)

前端发送的表单类型的请求体数据,可以通过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))

Django基础02 视图和模型_第12张图片

同样的request.POST也是一个QD对象,所以也可以用诸如request.POST['属性名']的样式获得请求体里的数据,不过一样是不推荐的,因为如果请求体里没有也一样会报错。

注意

request.POST只能用来获取post请求方式发送的表单格式数据,如果是非表单格式的,只能用下面的方式获取。

但是我们一般前端发送表单方式一般也只有get和post,所以我们默认如果使用表单就是post方法获取。

2.2 非表单类型的请求体数据(Non-Form-Data)
  • 非表单类型的请求体数据,或者是不用post请求方式发送的表单格式数据,Django无法自动解析,可以通过request.body属性获取最原始的请求体数据
  • 然后自己按照具体请求体原始数据的格式(JSON等)进行解析
  • 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请求

Django基础02 视图和模型_第13张图片

可以看出我们获得的二进制解码后的内容,杂乱无章,所以我们尝试使用之前的方式去获取的时候即使使用解码也获取不到还报错。对于这种格式我们得自己手动去解包,用正则去匹配到想要的内容。

​ 如果是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))

注意

  • 用这种方式如果发送空数据起码也要发个符合json格式的空数据,不然会报错。
  • json里传的数值如果是多个不需要使用getlist方法(也没有这个方法)。

3. URL路径参数:提取URL路径中特定部分数据

  • 在定义路由时,可以从URL中获取特定部分的路径参数
  • Django的路由系统会将提取的路径参数传递到视图的内部
  • path()和re_path()都可以提取路径参数

需求:

  • 需求1:

    http://127.0.0.1:8000/url_param1/18/
    
    • 提取路径中的数字18
  • 需求2:

    http://127.0.0.1:8000/url_param2/18500001111/
    
    • 提取路径中的手机号18500001111
1.path()提取路径参数

​ 当我们使用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明显是不可行的,因为默认的提取里没法判定一串数字是否是手机号码。

这时候我们就需要自定路径转换器了。

自定义路由转换器
  • 在任意可以被导入的python文件中,都可以自定义路由转换器
  • 比如:在工程根目录下,新建converters.py文件,用于自定义路由转换器

转换器是一个类,包含如下内容:

  • 字符串形式的 regex 类属性。
  • to_python(self, value) 方法,它处理匹配的字符串转换为应该传递到函数的类型。如果没有转换为给定的值,它应该会引发 ValueErrorValueError 被解释为不匹配,因此向用户发送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的提示。

Django基础02 视图和模型_第14张图片

Django基础02 视图和模型_第15张图片

2.re_path()提取路径参数

​ 可以发现如果没有默认转换器去自己定义的时候特别麻烦,我们可以使用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()),

注意

  • 使用re_path()的时候记得首尾绑定
  • 分组最好取别名,取了的别名记得要和函数里传的参数名字一样
  • 用正则目的有2个 一个是匹配内容 一个是获取正确内容
3.path() 和 re_path()的区别
  • path()语法相对简洁一些,如果没有路径参数要提取或者要提取的路径参数可以使用默认的路由转换器实现时,就选择path()。
  • re_path()语法相对复杂一些,但是,如果希望在匹配路由时,由自己编写所有的正则表达式,就选择re_path()。
  • 需要注意的是,在使用re_path()时,网络地址正则表达式一定要写完整,要有严格的开头和结尾

4. 提取请求头字符串数据

可以通过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方式都能获取

5. 其他常用HttpRequest对象属性

  • method:一个字符串,表示请求使用的HTTP方法,常用值包括:‘GET’、‘POST’。
    • 可以通过这个在函数视图里判断用的什么方法
  • encoding: 默认是utf-8 会返回一个None。
  • FILES:一个类似于字典的对象,包含所有的上传文件。
  • COOKIES:一个字符串,包含了浏览器自动发送的cookie缓存数据。
  • user:请求中认证出来的用户对象。
    • 请求对象的user属性返回的是请求过程中认证出来的用户对象
    • 使用方式:
      • user = request.user
    • 使用场景:
      • 从请求中获取当前登录用户对象信息
    • 注意点:
      • request.user获取到的不一定是当前登录用户对象
      • 如果当前请求是已登录用户发送的,那么request.user获取到的才是当前登录用户对象
      • 如果当前请求是未登录用户发送的,那么request.user获取到的会是一个AnonymousUser对象(匿名用户,没有任何用户信息,没有使用价值)。
    • 工作中如何使用request.user
      • request.user需要搭配用户访问的限制来使用。
      • 需要先判断用户是否已登录,如果用户已登录,就可以大胆放心的使用request.user

2.1.5 HTTPResponse对象

  • 视图在接收请求并处理后,必须返回HttpResponse对象或子对象。
  • HttpRequest对象由Django创建,HttpResponse对象或子对象由开发人员创建
  • 常见的响应方式:
    • HttpResponse():响应多种数据类型
    • JsonResponse():响应JSON
    • redirect():重定向
    • render():渲染并响应HTML模板

1. HttpResponse对象

示例:

# 测试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 默认响应状态码为 301
  • HttpResponsePermanentRedirect 默认响应状态码为 302
  • HttpResponseNotModified 默认响应状态码为 304
  • HttpResponseBadRequest 默认响应状态码为 400
  • HttpResponseNotFound 默认响应状态码为 404
  • HttpResponseForbidden 默认响应状态码为 403
  • HttpResponseNotAllowed 默认响应状态码为 405
  • HttpResponseGone 默认响应状态码为 410
  • HttpResponseServerError 默认响应状态码为 500

2. JsonHttp响应Json数据

  • 在开发功能时,如果前端需要JSON数据,那么后端就需要构造并响应JSON数据

  • 而Django提供了**JsonResponse**来构造并响应JSON数据

  • JsonResponse

    作用:

    • 帮助我们将响应的数据转换为JSON字符串
    • 设置响应头Content-Typeapplication/json

示例:

# 测试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)

3. redirect()重定向

需求:

  • 准备一个用于处理用户登录类视图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/')

4. redirect()重定向 搭配 反向解析

  • 能否可以实现一种效果,可以保证即使在版本迭代时,使用了新设计的地址替换了路由中原有的地址,我们之前编写的使用该地址的代码不用去修改,达到动态获取的目的。

解决方案:

  • 路由反向解析
  • 路由反向解析 是使用路由的别名,动态的解析出该路由中的真实地址

示例:

总路由中,给子应用的总路由起别名

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)

5. 中间件

5.1 中间件介绍

概念:

  • Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出
  • 中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性,其它的MVC框架也有这个功能
  • 实际上就是一种装饰器

使用场景:

  • 当某些操作在每次请求或响应时都会执行时,可以写在中间件中
  • 比如,每次发送post请求都要进行CSRF验证,就把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',
]
5.2 中间件方法
  • 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
      
5.3 自定义中间件
  • 中间件是一个独立的Python类,可以定义Django提供的六个方法中的一个或多个
  • 在工程根目录下,新建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
]
5.4 中间件执行顺序

准备两个自定义的中间件

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
]

浏览多个中间件的调用过程

Django基础02 视图和模型_第16张图片

重要提示:中间件执行顺序

  • 在视图被处理前(输入),中间件由上至下依次执行
  • 在视图被处理后(输出),中间件由下至上依次执行
  • 中间件的初始化方法,中间件由下至上一次执行

2.2 模型(Models)

2.2.1 准备数据库

安装mysqlclient依赖

pip install mysqlclient

如果也失败就先安装libmysqlclient-dev依赖

sudo apt-get install libmysqlclient-dev

拓展

早期Django代码使用的是Python2的时候的模块MySQLdb来连接mysql数据库,在版本迭代后已经无力再去修改里面的内容,且现在的Python3根本不支持mysqldb,而是使用pymsql模块连接,现在Django里需要你安装这俩个依赖,来使得在Django里输出的SQL语句能转换到pymsql里识别的语句。

2.2.2 模型类迁移建表

  • 我们需要了解ORM框架
  • 我们需要学会定义模型类
  • 我们需要学会迁移模型类建表

1.ORM框架

ORM框架介绍

  • O是object,也就是类或者对象的意思,这里的类就是模型类
  • R是relation,也就是关系数据库中数据表的意思
  • M是mapping,也就是映射的意思
  • 在ORM框架中,它帮我们把模型类和数据表进行了一个映射,可以让我们通过模型类及对象就能操作它所对应的数据表中的数据
  • ORM框架它还可以根据我们设计的模型类自动帮我们生成数据库中对应的数据表,省去了我们自己建表的过程

提示:

  • Django框架中内嵌了ORM框架,所以在使用Django框架时,我们不需要直接面向数据库编程
  • 而是定义模型类,通过模型类及对象完成数据表的增删改查操作

ORM框架作用:

  • 帮助Django的开发者以面向对象的思想去操作数据库。
  • 并且ORM框架也帮助程序员屏蔽了数据库之间的差异。

Django基础02 视图和模型_第17张图片

Django基础02 视图和模型_第18张图片

2.定义模型类

2.1 根据需求设计数据表
  • 需求:
    • "图书-英雄"管理
  • 分析关联关系:
    • 一本书里面会有多个英雄人物,每个英雄人物都会属于某一本书
    • 数据表一:图书信息表 (一方)
    • 数据表二:英雄信息表 (多方)
  • 绑定关联关系:
    • 外键定义在多方对应的数据表中,即,外键需要定义在英雄信息表中

Django基础02 视图和模型_第19张图片

2.2 定义模型类
  • 模型类被定义在子应用/models.py文件中
  • 模型类必须继承自Model类,位于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

3.模型类说明

3.1 关于主键
  • Django会为表创建自动增长的主键列,每个模型只能有一个主键列
  • 默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key
  • 如果使用选项设置某属性为主键列后Django不会再创建自动增长的主键列
3.2 关于属性命名
  • 不能是python的保留关键字

  • 不允许使用连续的下划线,这是由Django的查询方式决定的

  • 定义属性时需要指定字段类型,通过字段类型的参数指定选项,语法如下:

    属性 = models.字段类型(选项)
    

    表名取别名是用于在后端显示的时候会显示出来

3.3 关于数据库表名
  • 模型类如果未指明表名,Django默认以 小写app应用名_小写模型类名 为数据库表名
  • 但是,可通过模型的元类中的db_table自定义数据库表名
3.4 关于字段类型
类型 说明
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,对上传的内容进行校验,确保是有效的图片
3.5 关于字段选项
选项 说明
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 ,该字段就是必填的。

  • 1、默认是False的,如果设置为True的时候,django将会映射到数据表指定是否为空
  • 2、如果这个字段设置为False的时候,如果没给这个字段传递任何值的时候,django也会使用一个空字符串('')存储进去
  • 3、如果这个字段设置为True的时候,django会产生两种空值的情形(null和空字符串)
  • 4、如果想要在表单验证的时候允许这个字符串为空的时候,django建议使用blank=True
3.6 关于外键

外键设置格式为

 xxx = models.ForeignKey(关联的模型类名,on_delete=以下可选常量)

​ 在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据如何处理,在django.db.models中包含了可选常量:

  • CASCADE 级联,删除主表数据时连通一起删除外键表中数据
  • PROTECT 保护,通过抛出ProtectedError异常,来阻止删除主表中被外键应用的数据
  • SET_NULL 设置为NULL,仅在该字段null=True允许为null时可用
  • SET_DEFAULT 设置为默认值,仅在该字段设置了默认值时可用
  • SET() 设置为特定值或者调用特定方法
  • DO_NOTHING 不做任何操作,如果数据库前置指明级联性,此选项会抛出IntegrityError异常

​ 设置外键时,如果不指定,一般主表会多一个隐藏的字段名 从表小写_set可以用来查询主表单一对象所对应从表的多个对象,设置了related_name后则该字段名会替代了自带的隐藏字段。

3.7 关于多对多 ManyToManyField

​ 假设有2张表,房屋表和房屋设施表,一个房屋有多种设备,反过来,一个设备也是不止一个房屋有,此时二者的对象就是多对多,我们会设置ManyToManyField字段,此时django会自动帮你建立一张隐藏中间表,来记录二者的对应关系。

​ 我们简单使用的话,假如在House模型类里建立了facility多对多字段,此时house对象可以直接通过house.facility.all()取得所有该房屋对象所拥有的所有房屋设施对象。相反的,facility只能通过隐藏的facility.house_set.all()来取得拥有该设施的所有房屋对象。

4.迁移模型类 建表

4.1 生成迁移文件
python manage.py makemigrations

注意

如果显示没有添加任何修改 请去看setting.py是否去注册了子应用

4.2 同步到数据库中
python manage.py migrate

注意

谨记不要Django里建表后又手动去更改表结构,这样子django里的历史记录不正确,后面再度使用django更改的时候容易出错

5.添加数据(进入mysql添加)

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);

2.2.3 增删改查

1. shell工具 (用于在终端交互环境测试代码的)

Django的manage工具提供了shell命令,帮助我们配置好当前工程的运行环境(如连接好数据库等),以便可以直接在终端中执行测试python语句。

如果有需要,通过如下命令进入shell

python manage.py shell

导入两个模型类,以便后续使用

from booktest.models import BookInfo, HeroInfo

拓展 日志文件

在mysql配置文件里 有2项是和日志相关的,分别设置了日志文件目录,和日志文件是否打开。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9YdDGSw3-1611056255513)(asset/Snipaste_2020-12-09_10-00-02.png)]

默认是关闭的,解除注释后打开重启mysql服务器。

然后使用tail -f 日志文件路径 可以实时查看日志。

如果权限不足就加sudo。

Django基础02 视图和模型_第20张图片

2. 增加

增加数据有2种方法

2.1 save()
book = BookInfo()
book.btitle = '西游记'
book.bpub_date = '2020-05-18'
book.bread = 20
book.bcomment = 30
book.save()
2.2 create

类名.objects.create()添加

>>> BookInfo.objects.create(
... btitle='三国演义',
... bpub_date='2020-05-20',
... bread = 300,
... bcomment = 90
... )
<BookInfo: 三国演义>

3. 修改

3.1 save()
>>> hero = HeroInfo.objects.get(hname='猪八戒')
>>> hero.hanme = '猪悟能'
>>> hero.save()
3.2 filter().update()
>>> HeroInfo.objects.filter(hname='沙悟净').update(hname='沙僧')
1

如果找不到对应表数据则会显示0

4. 直接删除

4.1 delete()
>>> hero=HeroInfo.objects.get(id=13)
>>> hero.delete()
(1, {'booktest.HeroInfo': 1})
4.2 filter().delete()
>>> HeroInfo.objects.filter(id=14).delete()
(1, {'booktest.HeroInfo': 1})

5 查

Django里的查会把查询到的对象输出(打印)

1.all()
BookInfo.objects.all()
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>, <BookInfo: 三国演义>]>
>>> HeroInfo.objects.filter(hbook_id=1).all()
<QuerySet [<HeroInfo: 郭靖>, <HeroInfo: 黄蓉>, <HeroInfo: 黄药师>, <HeroInfo: 欧阳锋>, <HeroInfo: 梅超风>]>

查询所有符合内容的对象(表数据内容)

2.count()

查询符合结果内容的数量,也就是等于all()里的对象数量

>>> HeroInfo.objects.count()
17
>>> HeroInfo.objects.filter(hbook_id=1).count()
5
3.get()

取得单个对象,取不到的时候会抛出模型类.DoseNotExist异常

4.exclude()

筛选n’n’n’n’n’n以外的对象

6.过滤查询

实现SQL中的where功能,包括

  • filter 过滤出满足条件的多个结果
  • exclude 排除掉符合条件剩下的结果

过滤条件的表达语法如下:

属性名称__比较运算符=# 属性名称和比较运算符间使用两个下划线,所以属性名不能包括多个下划线

注意 小心混淆get和filter

>>> BookInfo.objects.filter(id__exact=1)
<QuerySet [<BookInfo: 射雕英雄传>]>
>>> BookInfo.objects.get(id__exact=1)
<BookInfo: 射雕英雄传>
6.1 exact 相等
BookInfo.objects.filter(id__exact=1)
可简写为:
BookInfo.objects.filter(id=1)
6.2 contains 模糊查询
>>> BookInfo.objects.filter(btitle__contains='传')
<QuerySet [<BookInfo: 射雕英雄传>]>
6.3 startswith、endswith 以指定开头结尾判断
BookInfo.objects.filter(btitle__endswith='部')

注意

以上条件查询都区分大小写,如果想不区分必须在各个区分条件之前加个i 比如icontains

6.4 isnull 空查询
>>> BookInfo.objects.filter(btitle__isnull=True)
<QuerySet []>
>>> BookInfo.objects.filter(btitle__isnull=False)
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>, <BookInfo: 三国演义>]>
6.5 范围查询

in是否在列表[]内

>>> BookInfo.objects.filter(id__in=[1,5,8])
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 西游记>]>

range是否在某个范围内

>>> BookInfo.objects.filter(id__range=(1,3))
<QuerySet [<BookInfo: 射雕英雄传>, <BookInfo: 天龙八部>, <BookInfo: 笑傲江湖>]>
6.6 比较查询
  • gt 大于 (greater then)
  • gte 大于等于 (greater then equal)
  • lt 小于 (less then)
  • lte 小于等于 (less then equal)
>>> BookInfo.objects.filter(id__gt=3)
<QuerySet [<BookInfo: 雪山飞狐>, <BookInfo: 西游记>, <BookInfo: 三国演义>]>
>>> BookInfo.objects.filter(id__gte=3)
<QuerySet [<BookInfo: 笑傲江湖>, <BookInfo: 雪山飞狐>, <BookInfo: 西游记>, <BookInfo: 三国演义>]>
6.7 日期查询

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))

7.F对象

之前的查询都是对象的属性与常量值比较,两个属性怎么比较呢? 答:使用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)

8.Q对象

多个过滤器逐个调用表示逻辑与关系,同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))

9.聚合函数

使用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函数的返回值是一个数字。

10.排序

使用order_by对结果进行排序

BookInfo.objects.all().order_by('bread')  # 升序
BookInfo.objects.all().order_by('-bread')  # 降序

11.关联查询

由一到多的访问语法:

一对应的模型类对象.多对应的模型类名小写_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为空,不会报错,获取的是空值
  • 结论:
    • 如果外键允许为空,那么务必使用方式二读取外键
    • 如果外键一定不为空,那么使用哪种方式读取外键都可以

2.2.4 查询集QuerySet

1.概念

Django的ORM中存在查询集的概念。

查询集,也称查询结果集、QuerySet,表示从数据库中获取的对象集合。

当调用如下过滤器方法时,Django会返回查询集(而不是简单的列表):

  • all():返回所有数据。
  • filter():返回满足条件的数据。
  • exclude():返回满足条件之外的数据。
  • order_by():对结果进行排序。

对查询集可以再次调用过滤器进行过滤,如

BookInfo.objects.filter(bread__gt=30).order_by('bpub_date')

也就意味着查询集可以含有零个、一个或多个过滤器。过滤器基于所给的参数限制查询的结果。

从SQL的角度讲,查询集与select语句等价,过滤器像where、limit、order by子句。

特点

  1. 查询集可以再次用过滤器过滤
  2. 查询集没内容不会报错只会返回一个空类列表

判断某一个查询集中是否有数据

  • exists():判断查询集中是否有数据,如果有则返回True,没有则返回False。

2. 两大特性

1)惰性执行

创建查询集不会访问数据库,直到调用数据时,才会访问数据库,调用数据的情况包括迭代、序列化、与if合用

例如,当执行如下语句时,并未进行数据库查询,只是创建了一个查询集qs

qs = BookInfo.objects.all()

继续执行遍历迭代操作后,才真正的进行了数据库的查询

for book in qs:
    print(book.btitle)
2)缓存

使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。

拓展

  • 查询集表示从数据库中获取的对象集合。具有自动缓存的特点。
  • 查询集自动缓存:
    • 使用同一个查询集,第一次使用时会发生数据库的查询,然后Django会把结果缓存下来,再次使用这个查询集时会使用缓存的数据,减少了数据库的查询次数。
  • 问题:
    • 如果某些数据需要频繁的更新,那么在查询和使用时就不能有缓存出现
    • 比如:实时更新库存和销量,库存和销量每次在使用时必须是最新的结果,不能是之前缓存中的结果
  • 结论:
    • 如果我们要频繁的更新数据时,那么要更新的数据不要使用查询集获取
    • 返回查询集的方法:all()、filter()、exclude()、order_by()
    • 不返回查询集的方法:get()
    • 所以如果要实时更新数据,建议采用get()查询要更新的数据

你可能感兴趣的:(Django基础,python)