django架构简介

Posted 此方家的空腹

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了django架构简介相关的知识,希望对你有一定的参考价值。

Django特性

  • ORM。models层建立数据类与数据库中数据形成ORM映射关系

  • MTV架构。与MVC架构有小小的区别,controller的功能被分化在了url dispatcher(urls.py文件)与views两个模块中

目录结构

Django把一个网站所有内容称作一个project,每个模块称作app

例如如下目录结构中,项目名称mysite为一个project, 文件夹mysite/mysite称为项目的根配置文件,其中的文件mysite/mysite/urls.py提供了url的一级路由,路由到各个app中,例如polls这个app

mysite/
    manage.py
    mysite/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py
    polls/
        __init__.py
        admin.py
        apps.py
        migrations/
            __init__.py
            0001_initial.py
        models.py
        static/
            polls/
                images/
                    background.gif
                style.css
        templates/
            polls/
                detail.html
                index.html
                results.html
        tests.py
        urls.py
        views.py
    templates/
        admin/
            base_site.html

部分模块功能解释如下

mysite\\mysite\\settings.py: 项目的配置项,类似于maven,有着如下形式的内容

from pathlib import Path

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-#3+p28vqi&m@pj(ito4frk2%57ic^27cn@ip-i4vu(4)z6ow43'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'polls.apps.PollsConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'mysite.urls'

TEMPLATES = [
    
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': 
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        ,
    ,
]

WSGI_APPLICATION = 'mysite.wsgi.application'


# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases

DATABASES = 
    'default': 
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    



# Password validation
# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    ,
    
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    ,
    
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    ,
    
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    ,
]

mysite\\mysite\\urls.py

我把它称作根路由或一级路由。之所以这么取名,因为在每个所谓app中,还有第二级url控制器

架构

用户从最右侧开始访问,请求(url)首先进入根目录下的url控制器(或称url dispatcher)中。url dispatcher中有着如下形式的函数

urlpatterns = [
    path('polls/', include('polls.urls', namespace="polls")),
    path('admin/', admin.site.urls),  # django提供的管理工具
]

这个url控制器可以称为一级路由。提供了project到app的映射

进入某个app后,还有二级路由,有着如下形式

from django.urls import path

from . import views

app_name = 'polls'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

可见,它提供了app到view层函数的映射

映射到view层某个函数(例如下面的"vote函数"),view会调用models层的数据与templates层的模板,render一个完整的页面返回给用户,有着如下的形式

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(request, 'polls/detail.html', 
            'question': question,
            'error_message': "You didn't select a choice.",
        )
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

可以看出,views层与url dispatcher共同承担了controller层的作用。在架构图中也可以看出,views层可以叫做django数据处理的中心模块也不为过

Models层中建立了一些POCO类,这些POCO类与数据库中的数据形成ORM映射,从而进行各种操作,示例代码如下

# Create your models here.
from django.db import models


class Question(models.Model):
    question_text = models.CharField(max_length=200)
    pub_date = models.DateTimeField('date published')


class Choice(models.Model):
    question = models.ForeignKey(Question, on_delete=models.CASCADE)
    choice_text = models.CharField(max_length=200)
    votes = models.IntegerField(default=0)

templates层也就是一些html文件,形式类似于jsp,例如下面这段代码

注意% code %中写逻辑代码,中写变量代码

<h1> question.question_text </h1>

<ul>
% for choice in question.choice_set.all %
    <li> choice.choice_text  --  choice.votes  vote choice.votes|pluralize </li>
% endfor %
</ul>

<a href="% url 'polls:detail' question.id %">Vote again?</a>

% endfor %
</ul>

<a href="% url 'polls:detail' question.id %">Vote again?</a>

参考

django框架简介及自定义简易版框架

web应用与web框架本质

概念

什么是web应用程序呢? Web应用程序就一种可以通过互联网来访问资源的应用程序, 用户可以只需要用一个浏览器而不需要安装其他程序就可以访问自己需要的资源.

应用软件通常有两种架构: B/S架构和传统的C/S架构. C/S架构是客户端/服务端程序, 用户需要访问服务器需要下载单独的客户端, 而B/S则是浏览器/服务端应用程序, 用户只需要选择兼容的合适的浏览器, 如IE, Chrome, Firefox等等来运行即可. Web应用程序通常就是属于B/S架构 ,这也是现在的主流软件架构.

Web应用程序是基于网络进行传输的, 而在网络上传输就需要通过socket, 一般都是通过TCP/IP协议来进行通信的, 因此我们可以这样理解Web应用, 浏览器就是Socket客户端程序, 而Web应用程序就是Socket服务端程序.

最简易socket服务端

有了这个认识, 客户端程序不需要我们负责, 我们就可以基于socket专心搭建一个服务端就可以完成简易版的Web应用了.

import socket

server = socket.socket()
server.bind(('localhost', 8080))
server.listen(5)

while True:
    # 开启接收客户端的程序
    conn, addr = server.accept()
    data = conn.recv(1024)
    # 这里打印看来自服务端
    print(data)
    # 返回响应信息
    response = 'Hello World!!!'
    conn.send(response.encode('utf-8'))
    conn.close()  # 关闭连接

基于简单的socket, 我们接受了来自服务端的请求, 获得了如下的请求信息

b'GET / HTTP/1.1\\r\\nHost: 127.0.0.1:8080\\r\\nConnection: keep-alive\\r\\nCache-Control: max-age=0\\r\\nUpgrade-Insecure-Requests: 1\\r\\nUser-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.90 Safari/537.36\\r\\nSec-Fetch-Mode: navigate\\r\\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\\r\\nSec-Fetch-Site: cross-site\\r\\nAccept-Encoding: gzip, deflate, br\\r\\nAccept-Language: zh-CN,zh;q=0.9\\r\\nCookie: csrftoken=mUcuvpw1xeYvCqI0VUWuxOEDORAMVOZ1JHAkJXXEiajSi8KisZx6sTRke9H3AEyf\\r\\n\\r\\n'

我们返回了信息给浏览器, 浏览器的显示是

技术图片

HTTP数据格式

这说明我们发送给浏览器的响应式无效的, Web应用程序想要在网络中完整的传输, 就一定需要遵从一定的通信协议,而在Web端这个应用层协议就是HTTP协议.

HTTP协议是有属于它自己的传输数据的格式的, HTTP的数据格式包含以下部分:

  • 请求首行 协议版本, 请求方式
  • 请求头 包含多个键值对形式的请求信息
  • \\r\\n
  • 请求体

服务端的响应格式也与之相对应:

  • 响应首行 协议版本, 状态码
  • 响应头 包含多个键值对形式的响应信息
  • \\r\\n
  • 响应体

返回正确响应

了解了HTTP的基本数据格式之后, 只要我们发送合法的响应信息, 就能和浏览器做一个基本的通信了, 然后继续修改上述的代码.

...
# 返回响应头
response = 'HTTP/1.1 200 OK\\r\\n\\r\\n'
# 返回响应体
response += '<h1>Hello World!!!</h1>'
...

只需要在响应加上响应首行和响应头信息, 并且我们可以在响应体中添加HTML标签, 这就可以让浏览器接收信息并正常解析出我们的响应信息了.

有了以上基础, 我们可以再修改代码, 让服务端返回一个html文件, 并在浏览器端正确渲染出来

...
# 返回响应头
response = 'HTTP/1.1 200 OK\\r\\n\\r\\n'
# 返回响应体
# response += '<h1>Hello World!!!</h1>'
with open('index.html', 'r', encoding='utf-8') as f:
    response += f.read()
...
index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>index</title>
    </head>
    <body>
    <h1 style="color: red">This is page index!</h1>
    </body>
    </html>

技术图片

返回动态的页面

最后浏览器返回了上面的结果. 但是现在又有了新的需求, 这样每次都是返回固定的静态页面, 我们需要返回一个动态的页面. 这就希望页面不要被写死, 而需要动态的获取参数来渲染出响应的页面.就以下面这个动态获取当前的时间页面为例.

...
with open('time.html', 'r', encoding='utf-8') as f:
    response += f.read()
response = response.replace(' now ', time.strftime('%Y-%m-%d %X'))
conn.send(response.encode('utf-8'))
...
time.html
        
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>time</title>
</head>
<body>
<h1 style="color: red">This is time page!</h1>
<p>Now time is  now  !</p>
</body>
</html>
        
    

最后的效果如下所示, 每次请求都显示不同的时间. 这里的思想也很容易, 就是用字符串的替换, 把特殊的字符换成我们最后需要

技术图片

根据不同请求返回不同页面

现在达成了动态网页的需求, 我们的新需求又来了, 需要能根据用户敲入的不同网址返回不同的信息, 而这就需要我们对来自浏览器的请求进行分析, 请求首行包含了我们需要的请求地址的信息. 这就需要我们对来自浏览器的请求体进行字符串的切割处理了, 来获取我们需要的数据.

import socket
import time

server = socket.socket()
server.bind(('localhost', 8080))
server.listen(5)


def index():
    with open('index.html', 'r', encoding='utf-8') as f:
        return f.read()


def get_time():
    with open('time.html', 'r', encoding='utf-8') as f:
        html = f.read()
    return html.replace(' now ', time.strftime('%Y-%m-%d %X'))


while True:
    # 开启接收客户端的程序
    conn, addr = server.accept()
    data = conn.recv(1024)
    # 这里打印看来自服务端
    print(data)
    # 这里进行字符串的切割, 先以换行符来切割, 在以空格切割
    path = data.decode('utf-8').split('\\n')[0].split(' ')[1]
    # print(path)
    # 获取了路径之后, 我们就能够根据不同的请求路径返回不同的信息了.
    response = 'HTTP/1.1 200 OK\\r\\n\\r\\n'
    if path == '/index':
        response += index()
    elif path == '/time':
        response += get_time()
    else:
        response += '404 error'

    conn.send(response.encode('utf-8'))
    conn.close()  # 关闭连接

我们的简易web服务端到了这已经有了点雏形了, 但是还不能支持并发, 并且我们还发现了前面的socket程序代码也是固定不变的, 处理浏览器请求头的过程是固定的, 返回的形式也是固定的, 而我们也只是简单的处理了请求路径的信息, 如果还需要其他信息, 就又要进行字符串的切割处理, 因此这样的处理工作是重复的, Python的原则是不要重复造轮子, 接下来就可以利用Python内置的wsgiref模块来完成前面这些固定的处理流程.

wsgi

wsgiref模块是一个遵从WSGI(web server gateway interface, web服务网关接口)协议, 那么什么是WSGI协议呢?在我们真实的生产环境中, 一般分为服务器程序和web应用程序.

  • 服务器程序 封装处理socket层面, 处理的是HTTP协议这一层的, 对请求的数据做处理, 然后交给应用程序处理
  • 应用程序则是负责具体的业务逻辑, 对来自浏览器的请求做业务逻辑层面的处理, 并返回相应的结果或页面返回.

在没有 WSGI 规范之前,一个服务器调度 Python 应用是用这种方式,另一款服务器使用的是那种方式,这样的话,编写出来的应用部署时只能选择局限的某个或某些服务器,达不到通用的效果。

所以,WSGI 的出现就是为了解决上面的问题,它规定了服务器怎么把请求信息告诉给应用,应用怎么把执行情况回传给服务器,这样的话,服务器与应用都按一个标准办事,只要实现了这个标准,服务器与应用随意搭配就可以,灵活度大大提高。

下面的图片说明了wsgi的工作流程.

技术图片

首先浏览器发送请求到服务端, 服务端对数据进行处理, 并把请求信息封装到environ字典中, 并调用一个应用程序(这通常是一个可调用对象)来处理业务逻辑请求, 当应用程序处理完业务逻辑之后, 会调用start_response这个回调函数来发送状态信息, 响应头部的信息和可能出现的异常错误信息. 发送完这个数据响应信息之后, 最后再返回一个可迭代对象(可以是字符串, 字典, 列表...)的数据信息给服务器程序. 最后再返回给客户端.

基于wsgiref模块的web程序

了解了什么是wsgi协议之后, 我们接下来就可以利用wsgiref模块来修改上面的应用程序了.

import time
from wsgiref import simple_server


# 视图部分, 具体的处理逻辑
def index():
    with open('index.html', 'r', encoding='utf-8') as f:
        return f.read()


def get_time():
    with open('time.html', 'r', encoding='utf-8') as f:
        html = f.read()
    return html.replace(' now ', time.strftime('%Y-%m-%d %X'))


def error():
    """请求路径不能匹配返回的信息"""
    return '404 error'


urls = [
    ('/index', index),
    ('/time', get_time)
]


def run(environ, start_response):
    """
    我们的app应用的入口函数
    :param environ: 服务器处理过后的请求参数都包含在里面了, 包含请求路径信息等
    :param start_response: 处理完数据后调用的响应回调函数
    """

    # 根据请求信息的不同, 来到不同的视图函数进行业务逻辑的处理
    path = environ.get('PATH_INFO')  # 包含了请求的路径信息
    func = None
    for url, f in urls:
        if url == path:
            func = f
            break
    # 如果url全部不匹配返回错误页面的信息
    res = func() if func else error()

    # 数据处理完毕, 这里要调用响应的回调函数, 进行处理, 响应头信息
    start_response('200 OK', [('Content-Type', 'text/html;charset=utf8'), ])
    # 最后返回可迭代数据信息
    return [res.encode('utf-8'), ]


if __name__ == '__main__':
    # 创建一个服务器应用程序, 绑定ip端口和要调用的app入口函数
    server = simple_server.make_server('localhost', 8080, run)
    server.serve_forever()  # 服务器永远启动着

上面的wsgiref模块实现的服务器严格的按照了wsgi协议, 我们自己实现的应用程序也遵守了wsgi接口规范, 这就让双方可以完美对接, 服务器程序和应用程序亦可以很大程度的实现解耦.

Django 简介

介绍

Django是一个由Python编写的具有完整架站能力的开源Web框架。使用Django,只要很少的代码,Python的程序开发人员就可以轻松地完成一个正式网站所需要的大部分内容,并进一步开发出全功能的Web服务。

Django本身基于MVC模型,即Model(模型)+View(视图)+ Controller(控制器)设计模式,因此天然具有MVC的出色基因:开发快捷、部署方便、可重用性高、维护成本低等。Python加Django是快速开发、设计、部署网站的最佳组合。

特点

Django具有以下特点:

  • 功能完善、要素齐全:该有的、可以没有的都有,常用的、不常用的工具都用。Django提供了大量的特性和工具,无须你自己定义、组合、增删及修改。但是,在有些人眼里这被认为是‘臃肿’不够灵活,发挥不了程序员的主动能力。
  • 完善的文档:经过十多年的发展和完善,Django有广泛的实践经验和完善的在线文档, 开发者遇到问题时可以搜索在线文档寻求解决方案。
  • 强大的数据库访问组件:Django的Model层自带数据库ORM组件,使得开发者无须学习其他数据库访问技术(SQL、pymysql、SQLALchemy等)。当然你也可以不用Django自带的ORM,而是使用其它访问技术,比如SQLALchemy。
  • 灵活的URL映射:Django使用正则表达式管理URL映射,灵活性高。
  • 丰富的Template模板语言:类似jinjia2模板语言,不但原生功能丰富,还可以自定义模板标签。
  • 自带免费的后台管理系统:只需要通过简单的几行配置和代码就可以实现一个完整的后台数据管理控制平台。
  • 完整的错误信息提示:在开发调试过程中如果出现运行错误或者异常,Django可以提供非常完整的错误信息帮助定位问题。

参考

  1. 参考1

  2. 参考2

以上是关于django架构简介的主要内容,如果未能解决你的问题,请参考以下文章

django架构

Python之Web架构Django部署教程

如何使用Django 结合WebSocket 进行实时目标检测呢?以yolov5 为例,实现:FPS 25+ (0: 系统简介与架构)

django框架简介及自定义简易版框架

Django2.2架构+ubuntu16(华为云)+python3.6架设“文学天地”个人网站

Django简介