TOC

Django简介

    Django它是Python当中一个非常有名的重框架,这个框架采用MVC设计架构,它遵照MVC框架的这种设计思想,形成的一个开源的Web快速开发框架,它是一个框架,它有自己内部的机制,各种架子都搭好了,我们只需要做一些定制化的一些需求即可;
    对于Django来讲,它是一个重框架,内部功能大而全,自带ORM模型系统、Template模版系统、Form表单系统、Auth认证组件等核心组件,另外一个比较流行的Flask框架,上述的这些比较核心的组件都没有,需要我们自己去找第三方的实现,然后结合我们的Flask组合使用;
    所以对于Django框架在目前来讲,使用非常之广泛,主要是因为它能够达到快速开发的效果,很多的核心组件都集成在其中,企业为了达到快速开发的目的,就采用了该框架,企业讲的效率;
    此外,Django还提供了一个内建的后台管理,我们只需要稍微做一些简单的配置就可以进行后台的管理了,那么在Django周边还提供了很多的第三方插件,这也是很多人选择它的原因之一,Django因为它使用比较广泛,但是它内部并不是很多功能都设计好了,所以它提供了一个开放接口,可以让广大开发者自己以接口的形式,来丰富Django内部的功能;

Django缺点

    Django主要有量大缺点,优点即缺点,因为Django的框架大而全,所以它是一个非常重的框架,内部的东西太多,然后Django是同步阻塞类型的网络模式,但是在3.0之后就改成异步非阻塞,但是在目前来讲,3.0才刚刚起步,大部分项目都是采用2.x甚至1.x的版本,都是同步阻塞类型的,所以它适用于一些企业级网站开发,无法适用于大型网站的首选框架;

Django安装

    Django有很多版本,目前长期支持版本有两个1.11和2.2,大部分企业都会选择这种官方长期支持的版本,另外,Django的3.x直接从3.6开始,目前来讲使用还是比较少的,框架太新了,生态还不够全,有很多插件还不支持这么较新的Django版本,所以此次就主要使用Django 2.2版本为学习版本;
[cce@doorta ~]# pip3.5 install django===2.2

Django命令行工具

    使用Django框架自带的命令行,可以直接创建Django项目,也可以将Django项目启动为一个HTTP Server运行在指定的TCP端口之上,如下;
# 创建项目目录
[cce@doorta ~]# mkdir /usr/local/Project/blog
# 创建项目
[cce@doorta ~]# django-admin startproject blog /usr/local/Project/blog
# 在项目下面创建App目录
[cce@doorta ~]# mkdir /usr/local/Project/blog/user
# 创建app
[cce@doorta ~]# django-admin startapp user /usr/local/Project/blog/user
# 项目结构
[cce@doorta ~]# tree /usr/local/Project/blog/    
├── blog
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── user
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── migrations
    │   └── __init__.py
    ├── models.py
    ├── tests.py
    └── views.py
# 启动项目
[cce@doorta ~]# python3.5 /usr/local/Project/blog/manage.py runserver 8080
August 05, 2022 - 03:42:57
Django version 2.2, using settings 'blog.settings'
Starting development server at http://127.0.0.1:8080/
Quit the server with CONTROL-C.

Django项目结构

    对于Django项目有几个主要的文件,如下;
manager.py:负责本项目管理的命令行工具,如应用创建、数据库迁移等都使用它来完成;
settings.py:项目的核心配置文件,主要有应用配置、数据库配置、模版配置、静态文件配置、中间件配置、日志配置和第三方插件配置;
urls.py:URL路径映射配置,配置路由系统;
wsgi.py:定义WSGI接口信息,一般无需改动;
项目配置文件
    settings.py配置文件是Django框架的重要配置文件,它定义的一些全局变量用来给Django框架传递参数,我们还可以根据自己的实际需求来修改这个文件从而实现某些特定的要求,下面我们对这个配置文件进行详细介绍,了解这个配置文件,是迈进Django世界的重要一步;
BASE_DIR:项目文件夹绝对路径,即项目的主路径;
SECRET_KEY:这个变量的本质是一个加密的盐,它一般配合加密算法 Hash、MD5 一起使用。例如用户密码的加密或者建立会话时用到的 sessionid 都需要用到 SECRET_KEY 。在实际的开发工作中,一般将它保存在系统的环境变量中以确保加密盐的安全;
DEBUG:是否开启调试模式,也称为开发模式,开发模式下,项目的运行过程中会暴露一些错误信息以方便调试;
ALLOWED_HOSTS:表示允许哪些主机来访问该项目,当 DEBUG = False 时,必须填写,有三种填写方式,第一种"[""]"表示只有localhost可以访问,第二种"["*"]"表示任何主机可以访问,第三种["192.168.2.3","192.168.1.3"]只有指定IP可以访问;
INSTALLED_APPS:表示已经在当前Django项目注入的App,如果新建App,需要加入到此列表中;
MIDDLEWARE:中间件加载列表;
ROOT_URLCONF:指定路由的配置文件,它是Django路由系统的入口;
TEMPLATES:指定模版文件的跟路径和模版引擎等相关配置;
WSGI_APPLICATION:Django项目的基础配置;
DATABASES:数据库相关配置;
AUTH_PASSWORD_VALIDATORS:这是一个支持插拔的密码验证器,且可以一次性配置多个,Django 通过这些内置组件来避免用户设置的密码等级不足的问题;
LANGUAGE_CODE:语言类型,默认为英文en-us,中文可以设置为zh-hans;
TIME_ZONE:时区类型,默认为UTC,国内可以修改为Asia/Shanghai;
USE_I18N:可以选择向不同国家的用户提供服务,那么就需要支持国际化和本地化。USE_118N 和 USE_L10N 这两个变量值表示是否需要开启国际化和本地化功能。默认开启的状态;
USE_L10N:可以选择向不同国家的用户提供服务,那么就需要支持国际化和本地化。USE_118N 和 USE_L10N 这两个变量值表示是否需要开启国际化和本地化功能。默认开启的状态;
USE_TZ:是否启用时区;
STATIC_URL:表示静态文件的目录,如CSS、JavaScript、Images;
DEFAULT_CHARSET:全局设定字符编码;
自定义配置
    对于配置来讲,也可以将一些自定义的配置加入到settings.py配置文件当中,然后,我们可以直接在项目中引用该配置,如下;
# settings.py
ALIPAY_KEY='ASDG3123rDSasdegrstWRE'
# 引入方式
from django.conf import settings
print(settings.ALIPAY_KEY)

Django应用注册

    那么在我们的Django项目中创建出一个user应用之后,默认情况下,这个user应用是没有加载到我们的Django框架当中的,也就是说它目前来讲,只是一个独立的Python包,如果需要将它作为Django项目当中的一个子应用,我们应该将这个应用加载到Django项目的settings.py配置文件里面的INSTALLED_APPS配置当中,成为当前Django项目中的一个应用,如下;
# 项目结构
[cce@doorta ~]# tree /usr/local/Project/blog 
/usr/local/Project/blog
├── blog
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
└── user
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── migrations
    │   ├── __init__.py
    ├── models.py
    ├── templates
    │   └── index.html
    ├── tests.py
    └── views.py
# 在Django项目的settings.py配置文件中注册user应用
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'user' # 注册user应用
]

URL地址

    URL在计算机领域的定义为统一资源定位符(Uniform Resource Locator),它的作用就是标识了某个资源具体的位置,它是www的统一资源定位标志,简单地说URL就是web地址,俗称"网址",URL的一般语法格式为,protocol://hostname[:port]/path[?query][#fragment],如下;
URL示例:http://baidu.com/video/showVideo?menuld=657421&version=AID999#subject
protocol:即协议,上述的http就是协议,常见的有http、https、file等;
hostname:指存放资源服务器的域名系统,可以是主机名、IP或者域名,如上述的www.baidu.com;
port:端口号,默认为80;
path:即路由地址,由零个或者多个"/"隔开的字符串表示,路由地址决定了服务端如何处理这个请求,上述的/video/showVideo就是路由;
query:查询字符串,一般用于访问动态网页时传递参数,可以有多个参数,使用"&"隔开,每个参数的key和value使用"="表示,如上述的?menuld=657421&version=AID999;
fragment:瞄点,定位一个网页中一个具体的位置;
    拿Django的路由举例,当浏览器向我们的Django项目请求一个URL为http://127.0.0.1:8080/page/2022时,Django会根据ROOT_URLCONF中找到主路由的配置文件,默认情况下,该配置文件在项目同名目录下的urls.py文件中,当找到该文件之后,会直接从该文件的urlpatterns路由数组中一次匹配上述的PATH(/page/2022),匹配成功之后,会调用对应的视图函数处理此次请求,然后返回响应结果,处理失败则返回404响应;

视图函数

    在Django中,有一个视图函数的说法,说白了,它就是我们上面说的路由对应的处理函数,但是我们需要知道的是,视图函数并非指一个函数,它可以是一个函数,也可以是一个类,此外,该视图函数主要用于处理用户的HTTP请求,根据Django的项目目录结构当中,我们应当将一个视图放在Django项目内部应用文件夹下面的view.py文件中;
    此外,视图还比如有一个要求,就是该函数,或者该类下面的处理方法必须具备一个参数,该参数,根据WSGI的协定,主要是用来接收,WSGI Server发送过来的environ对象,该对象包含了HTTP请求的详细信息;
    在早期,视图开发的过程中存在一些常见的语法和模式,于是引入基于函数的通用视图来抽象这些模式,并简化了常见情况下的视图开发。因此,刚开始的时候只有FBV,而Django所做的事情就是向定义的视图函数传递一个HttpRequest,并希望返回一个HTTP请求的Request对象;
    但是,基于函数的通用视图是有问题的,问题在于它很好地覆盖了简单的情况,但针对稍微复杂的场景,它没有办法在某些配置项之外进行扩展或自定义,从而极大地限制了它在许多实际应用程序中的实用性。而考虑到扩展性与自定义,这正是面向对象技术的强大之处,于是诞生了CBV;
    Python是一个门面向对象的编程语言。如果我们只用函数来编写视图函数,那么就会造成很多面向对象的优点无法利用起来,比如说封装、继承、多态等。这也是Django之所以加入了 CBV 模式的原因,它可以让开发者使用类的形式去编写View视图函数;
基于函数的视图
    FBV(function based views),即基于函数的视图,一个路由对应一个函数,从而实现路由到函数的映射,如下示例;
def home(request):
    pass
基于类的视图
    CBV是基于类的视图,就是使用类来处理用户的请求,不同类型的请求我们可以在类中使用不同方法来处理,这样大大的提高了代码的可读性,而不是像FBV那样使用条件分支代码。但是定义的类要继承父类View,所以我们在使用CBV的时候需要提前引入库django.views包下的View基类;
    那么同样的,对于CBV中的处理请求的方法,也必须有一个参数来接受WSGI Server传递过来的environ对象,如下示例;
from django.views import View
class home(View):
    def get(self,request):
        pass
    def post(self,request):
        pass
    同时,对于CBV需要注意一点就是,对于Django的路由一般都是分配一个函数,而不是一个类,那么对于CBV来讲,它主要还是通过父类View提供的一个静态方法as_view()来实现的,as_view()方法是基于类的外部接口,他返回一个视图函数,调用后请求会传递给dispatch方法,dispatch方法再根据不同请求来处理不同的方法。
    所以,我们在路由的配置时,如果我们的处理函数是一个类,而不是一个函数,那么我们需要,调用视图类的as_view()方法,如下;
from user.views import home

urlpatterns = [
    path('', home.as_view()),
]

请求与响应

    对于一个Django下面,当浏览器发起一个HTTP请求时,首先会被Django内部的WSGI Server接收到,然后WSGI Server会将这个请求报文解析成一个名为environ的字典对象,然后将这个对象使用WSGIRequest包装成一个遵循HTTP协议的Request对象,然后由WSGI Server将这个Request对象发送给Django App,随后由Django App负责将这个请求进行路由匹配,然后进行处理,最后根据处理结果,构建响应头部,构建响应报文,然后给客户端返回一个HtppResponse对象,由此,完成一次HTTP事物;
HttpRequest
    通过源码来看,当一个浏览器客户端发起请求时,首先,WSGI Server会将这个请求报文封装成environ对象,该对象包含了HTTP请求的详细信息,然后将这个对象交给WSGI Server封装成一个WSGIRequest对象,因为HttpRequest是WSGIRequest的父类,所以该对象就是一个HttpRequest对象,然后将封装好的HttpRequest交给Django App进行处理,如下;
class WSGIHandler(base.BaseHandler):
...
    request_class = WSGIRequest # 设置类属性
    def __call__(self, environ, start_response):
...
        request = self.request_class(environ) # 将WSGI Server封装的environ交给WSGIRequest
...
class WSGIRequest(HttpRequest): 
...
    WSGIRequest将HTTP的请求报文进行了封装,从而得到了一个遵循HTTP协议的Request请求报文,可以通过HttpRequest类看到,它下面有很多报文数据提取方法,如提取主机、端口、协议等,常用属性和方法如下;
# HttpRequest对象属性
GET:获取GET请求的查询字符串;
POST:获取POST请求的查询字符串;
COOKIES:获取Cookie值;
META:获取请求头;
FILES:获取上传的文件对象;
path:获取请求路径,包含假名;
path_info:获取请求路径,不包含假名;
method:获取请求方法;
content_type:获取请求的文件类型;
content_params:获取CONTENT_TYPE 标题中包含的键/值参数字典;
# HttpRequest对象方法
_get_scheme:获取请求的协议,http或https;
build_absolute_uri:请求的全URL,包含query_str和请求域;
get_full_path_info:请求的全URL,包含query_str但是不包含请求域;
get_full_path:和get_full_path_info类似,大部分情况下都一样,但是推荐使用get_full_path_info,因为它不包含程序假名,具体请查看官网;
get_host:返回请求的主机名;
get_port:返回请求的端口;
get_raw_uri:请求的全URL,包含query_str和请求域;
QueryDict
    通过源码可以看到,WSGI Server将请求报文解析成了一个environ对象,该对象包含了HTTP请求的详细信息,然后WSGI Server又将其封装成了一个WSGIRequest类的对象,而该对象下面的GET属性,是一个QueryDict对象,该对象的返回值是WSGI Server从environ对象里面获取到的QUERY_STRING字符串,也就是URL中的query_string字符串,所以request.GET和request.POST属性值就是query_string字符串,如下源码;
class WSGIRequest(HttpRequest):
    @cached_property
    def GET(self):
        raw_query_string = get_bytes_from_wsgi(self.environ, 'QUERY_STRING', '') # 从environ字典中获取到QUERY_STRING值
        return QueryDict(raw_query_string, encoding=self._encoding) # 将其封装成一个QueryDict对象
...
class QueryDict(MultiValueDict):
    def __init__(self, query_string=None, mutable=False, encoding=None):
...
        for key, value in limited_parse_qsl(query_string, **parse_qsl_kwargs): # 将其封装到列表中,然后调用QueryDict父类的父类(dict)的__repr__返回;
            self.appendlist(key, value)
        self._mutable = mutable
HttpResponse
    Django服务器接收到客户端发送过来的请求后,会将提交上来的这些数据封装成一个HttpRequest对象传给视图函数。那么视图函数在处理完相关的逻辑后,也需要返回一个响应给浏览器。而这个响应,我们必须返回HttpResponseBase或者他的子类的对象。而HttpResponse则是HttpResponseBase用得最多的子类,如下;
content:返回的内容,可以是一个bytes对象,也可以是一个字符串;
status_code:需要构建响应报文的响应码,默认为200;
content_type:返回的数据的MIME类型,默认为text/html。浏览器会根据这个属性,来显示数据。如果是text/html,那么就会解析这个字符串,如果text/plain,那么就会显示一个纯文本。常用的Content-Type如下;
    text/html:默认的,html文件;
    text/plain:纯文本;
    text/css:CSS文件;
    text/javascript:JS文件;
    multipart/form-data:form-data对象属性;
    application/json:JSON类型数据;
    application/xml:XML类型数据;
charset:响应报文主体的编码格式;
    在django.http包下也提供了一个HttpResponse方法,它可以直接将我们的字符串或者二进制对象封装成一个Response响应报文,所以我们可以直接将一个字符串传递给HttpResonse方法,得到一个HTTP的Response对象,然后直接返回给浏览器端;
# views.py
from django.http import HttpResponse
class home(View):
    def get(self, request):
        return HttpResponse("OK")

[cce@doorta /usr/local/Project/blog]# curl -sIL -w '%{content_type}' -o /dev/null http://127.0.0.1:8000
text/html; charset=utf-8%
JsonResponse
    JsonResponse也是继承HttpResponse,其内部处理和上面我们演示的原理相同,也是把content内容先dumps成json字符串,content_type='application/json'这两个规则,此时,在使用时直接返回可以dumps的content数据即可,不需要自己再去做dumps操作;
    所以我们直接使用HttpResponse也是可以直接给客户端返回一个JSON对象,只需要在传递给HttpResponse属性参数时,将content中要返回的内容使用json模块的dumps方法转换成json字符串,然后将构建出来的Response对象的content_type头信息设置为application/json即可;
# views.py
from django.http import JsonResponse
class home(View):
    def get(self, request):
        dic = {'name': 'cce', 'age': 18}
        return JsonResponse(dic)

[cce@doorta /usr/local/Project/blog]# curl -sIL -w '%{content_type}' -o /dev/null http://127.0.0.1:8000
application/json%

路由

    路由,从表现形式中来讲,就是客户端会通过不同的URL访问过来,期望得到不同的资源,那么这个时候我们就需要建立一个URL和请求处理函数的映射关系,也就是说,想要实现访问不同的URL拿到不同的资源,就需要将不同的URL对应不同的处理逻辑,建立映射关系,这个映射,在Python当中一般称之为路由;
    路由既然是不同的URL对应不同的处理函数,那就说明,什么样的URL路径应该对应什么样的处理函数,这就需要做匹配原则,那么这个匹配规则,分为绝对匹配和模式匹配,要么是直接相等,要么被正则表达式模式匹配;
    Django路由的配置需要在项目的urls.py里面进行配置,当然,也可以多级配置,一个应用中,建立一个urls.py文件建立路由映射,然后在urls.py文件创建一个urlpatterns路径映射列表,如下;
path函数
    对于Django的路由配置,在Django 2.x版本之前主要采用url函数来实现,那么在Django 2.x之后,就更换成了path函数,利用path函数来进行路径处理,当然,在Django 2.x之后也保留了url函数,只不过将url函数更名为re_path如下,不过推荐使用新版的path函数来实现路径映射;
from django.urls import path
语法:path(route,view,kwargs=None,name=None)
    route:匹配URL中的请求路径;
    view:指定该路径对应的视图处理函数;
    kwargs:视图使用的字典类型的参数;
    name:为地址分配别名,用来反向获取URL标识;
    如下,就是测试path函数的view视图处理函数,它处于Django下面下的应用下的views.py文件,如下;
# views.py
from django.http import HttpResponse
class Home(View):
    def get(self, request,id=None):
        if id:
            print(id)
        return HttpResponse("Test Page")
根路径匹配
    对于path函数的根路径匹配,它不是向path函数传递一个根"/",而是传递一个空白字符,这样才能代表根路径的匹配,这是Django 2.x之后的path函数要求的一点,如下;
# urls.py
from user.views import Home
urlpatterns = [
    path('', Home.as_view()),
]

[cce@doorta ~]# curl http://127.0.0.1:8000
Test Page%
PATH转化器
    之前说过HTTP请求中传递数据的三种方式,第一种是使用查询字符串,第二种使用post进行数据传递,第三种,就是将URL当中传递数据,因为request对象中包含请求的URL,所以作为服务端我们可以使用正则表达式对其进行匹配,进行数据提取,那么在path函数下也支持这样操作,并且它将这种方式,封装得更加优雅了;
    那么PATH转化器的主要功能,就是在URL当中提取我们想要的数据,它内部也是使用的正则表达式进行处理的,但是我们不能直接使用正则表达式对其进行匹配;
# 基础匹配
urlpatterns = [
    path('<id>/', Home.as_view()),
]
[cce@doorta ~]# curl -sIL -w '%{http_code}' -o /dev/null http://127.0.0.1:8000/a/ # 匹配成功,URL当中的a将以实参的方式传递给视图函数的id参数;
200%  


# 限制类型
urlpatterns = [
    path('<int:id>/', Home.as_view()),
]
[cce@doorta ~]# curl -sIL -w '%{http_code}' -o /dev/null http://127.0.0.1:8000/a/ # 此时将匹配失败
404% 
  • 注意一:需要注意的是,一旦我们使用了PATH转化器,那么我们的视图函数就需要接收多个参数了,我们可以使用;
  • 注意二:对于path函数来讲,默认以根"/"开头,也就是说,在编写路由时,不允许写"/",同时在路由字符串结尾时带"/"和不带"/"是有区别的,拿"index/"为例,带"/"时,会匹配"/index"也会匹配"/index/",那么不带"/"时,只会匹配"/index",这点在开发中需要注意;
re_path函数
    前面说过,针对Django 2.x版本之后,Django官网将原Django 2.x之前版本的URL处理函数url修订为path函数,那么在Django 2.x之后其实也保留了对url函数的支持,只不过将其名称修改为了re_path;
from django.urls import re_path
语法:path(regex,view,kwargs=None,name=None)
    regex:正则模式匹配URL中的请求路径;
    view:指定该路径对应的视图处理函数;
    kwargs:视图使用的字典类型的参数;
    name:为地址分配别名,用来反向获取URL标识;
    第一个参数为regex,它仅支持命名分组,和无名分组的匹配模式,命名分组的格式为(?P<name>patten),匹配成功后会以关键参数的形式传递给视图函数,当然,也支持无名分组;
    需要注意的是,命名分组有一个要求,分组名称必须和视图函数的形参名称一致;
# 无名分组
urlpatterns = [
    re_path(r'(\d+)', home),
]
# 有名分组
urlpatterns = [
    re_path(r'^(?P<id>\d+)$', home),
]
include函数
    Django作为一个Web框架,那么当我们的网站发展到一定程度之后,可能会新增很多的子模块,即应用,那么当应用非常多之后,我们的路由配置文件可能会显得非常臃肿,比如一个博客网站,可能有博文应用、后台应用、评论点赞应用等等等;
    那么此时,我们可以想象一样,如果当我们的博客发展到十个应用之后,每个应用下有数十个接口,每个接口对应一个路由,那么这样来说,我们的整个项目就有上百个路由配置,上百个路由配置,全部都写入主项目下面的urls.py配置,这样会显得非常的臃肿,肉眼检索起来也非常的麻烦,所以,Django也提供了一个功能,它是允许每个应用都可以有自己独立的路由配置,这个路由配置,可以使用include函数,加载到主路由配置当中,类似nginx配置的include关键字;
    但是需要知道的是,默认情况下,我们的Django App创建完成之后,并不会在应用目录下创建路由配置文件,所以此,文件,我们应该自行创建,如下;
    语法结构:include( 应用.路由配置文件 )
# 应用路由配置
from django.urls import path
from user.views import Home

urlpatterns = [
    path('home/', Home.as_view()),
]
# 主路由配置
from django.urls import path, include

urlpatterns = [
    path('user/', include('user.urls')),  # 使用include函数,将应用下的路由加载当当前路由配置当中
]
    可以看到上述的加载应用自定义路由的方式,主要是通过include函数来实现的,那么加载进来之后,如果我们希望访问user应用下的某个接口,那么我们的URL路径将不再是以"/"开始,而是"/user/"开始,也就是说,如果我们想要访问到应用下面的"home/"时,访问地址,应该是http://hostname:port/user/home;

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注