Loading...

Django 网站全栈开发教程

这篇文章是在 Python 基础知识全通关的基础上进行的进阶学习。

注意:由于本博客主题使用 Prism.js 对代码段进行渲染,渲染过程中,会与代码块中的双 {{ + % 的语法产生冲突,因此,本文将所有相关符号都替换为 {.{{.% 形式,以解决代码渲染的问题。读者在阅读和拷贝代码的过程中,请务必注意该问题并忽略符号之间多出的 .,如因此造成阅读上的不便,还请见谅。

学习前提

知识储备要求

  1. 了解 Python 基础,如果不了解,请先行阅读《Python 基础知识全通关》。
  2. 拥有前端开发相关基础技能,至少包括:CSS、JS、HTML。
  3. 会使用常用的 Liunx 命令。

环境说明

本篇文章在编写过程中,将以如下信息作为参考环境:

  1. 系统信息:[VMware 16.1.2]{.label .primary} + [CentOS 8.4]{.label .info}, [windows10]{.label .info}
  2. Python 版本:[Python 3.9.6]{.label .success}
  3. 开发工具:[Pycharm]{.label .danger}
  4. 命令行工具:[Termius]{.label .warning}

关于 linux 环境,本文仅在安装、部署等少数部分提供相关说明,其他大部分环境将在 windows 下进行。

简介

Django 是一个由 Python 编写的一个开放源代码的 Web 应用框架,Django 本身基于 MVC 模型。

Django 框架使用 MTV 模式,但其本质上和 MVC 是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同,Django 的 MTV 分别是指:

  1. Model:编写程序应有的功能,负责业务对象与数据库的映射(ORM)。
  2. Template:负责如何把页面(html)展示给用户。
  3. View:负责业务逻辑,并在适当时候调用 Model 和 Template。

除了以上三层之外,还需要一个 URL 分发器,它的作用是将一个个 URL 的页面请求分发给不同的 View 处理,View 再调用相应的 Model 和 Template。

Python MTV

安装与初始化

虚拟环境安装

pip3 list # 查看列表,检查是否存在virtualenv
pip3 install virtualenv -i https://pypi.douban.com/simple # 不存在则安装

注:安装 Python 环境,默认会安装 pip 工具包,可以通过如下命令统一指定镜像源:

pip config set global.index-url https://pypi.douban.com/simple/

创建并进入虚拟环境:

# 为简洁起见,此处将cd命令合并为长目录
mkdir /app/projects/django_venv
virtualenv /app/projects/django_venv
source /app/projects/django_venv/bin/activate
# 此时虚拟环境已启动成功,命令行带有前缀(django_venv)
pip3 list

如需退出虚拟环境,使用如下命令:

deactivate # 退出虚拟环境

提示:

  1. 如果在虚拟机创建过程中提示 -bash: virtualenv: command not found ,这是因为 /usr/bin 中还未创建软连接。
  2. 查找 virtualenv 所在位置:find / -name virtualenv (稍微耗时一点)。
  3. 创建 virtualenv 软连接:ln -s /usr/local/bin/python3/bin/virtualenv /usr/bin/virtualenv

安装 Django

进入虚拟环境,并为该虚拟环境安装 Django。

pip3 install django==3.2.8 -i https://pypi.douban.com/simple

创建并启动网站工程:

cd /app/projects
django-admin startproject firstsite # 创建项目
python firstsite/manage.py runserver # 启动项目

测试访问:

First Django

注:如需通过外部网络访问虚拟机内部提供的服务,需要配置相应的防火墙出入站规则。

另外,如需共享文件夹,可参考:

windows 下安装

在 win10 环境下安装 python3 很简单,如果未安装,只需要在命令行输入 python3 即可进入微软商店免费获取,点击下载安装即可。

创建文件夹 A:\python-venv\django_venv

C:\Users\Administrator>virtualenv A:\python-venv\django_venv # 创建虚拟环境
A:\python-venv\django_venv\Scripts>activate # 激活虚拟环境
(django_venv) A:\python-venv\django_venv\Scripts>pip3 install django==3.2.8 -i https://pypi.douban.com/simple # 为虚拟环境安装django
(django_venv) A:\python-venv\django_venv\Scripts>cd A:\pycharm-workspace
(django_venv) A:\pycharm-workspace>django-admin startproject firstsite # 创建项目
(django_venv) A:\pycharm-workspace>python firstsite/manage.py runserver # 启动项目

其实总体上,linux 和 windows 平台的安装没有太大区别。

在 Pycharm 中,可以通过如下方式制定虚拟环境:

Pycharm venv

在 Pycharm 中启动项目就更简单了,直接点击右上角的运行按钮即可。

项目结构

在 Pycharm 中配置好环境,后续操作在编辑器中进行,将会更加方便。

生成页面子应用:

python manage.py startapp index

执行完成后,目录结构如下图所示:

Django Project Structure

项目配置

与项目同名的子文件夹中存放了项目相关配置,部分文件说明如下:

  • settings.py - 项目配置

    # 返回项目绝对路径
    BASE_DIR = Path(__file__).resolve().parent.parent
    
    # 数据加密,放置跨域攻击
    SECRET_KEY = 'django-insecure-7)s64=kl*o2zm$wc86k&s(4*&!%a)ecs_k&jskmai2dmss10$8'
    
    # 是否处于开发环境
    DEBUG = True
    
    # 白名单,*表示所有
    ALLOWED_HOSTS = ['*']
    
    # 应用注册
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'index',
    ]
    
    # 项目根路由
    ROOT_URLCONF = 'firstsite.urls'
    
    # 配置开发服务器
    WSGI_APPLICATION = 'firstsite.wsgi.application'
    
    # 配置数据库
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': BASE_DIR / 'db.sqlite3',
        }
    }
    
    # 用户密码加密
    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',
        },
    ]
    
    # 网站默认语言 # zh-hans
    LANGUAGE_CODE = 'en-us'
    TIME_ZONE = 'UTC' # 时区 Asia/Shanghai
    
    # 模板配置
    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'templates')], # 指定模板文件位置:当前项目下的templates文件夹
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    
    # 指定静态文件位置
    STATICFILES_DIRS = [
        (os.path.join(BASE_DIR, 'static'))
    ]
  • urls.py - 路由配置

    编辑 index/views.py

    from django.shortcuts import render
    from django.http import HttpResponse
    
    def index(request):
        return HttpResponse('Hello world')  # 返回数据

    urls.py 中可以通过如下方式指定路由:

    from django.contrib import admin
    from django.urls import path
    from index.views import index
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', index),  # 路由绑定视图
    ]

    此时运行并访问,页面将会输出 Hello world 字样。

  • templates - 模板配置

    templates 目录下可创建 html 模板文件。

    在 templates 同级目录下可创建 static 文件夹,用于存储静态文件,静态文件位置需要在 settings.py 中指定,见上文。

    在 static 文件夹下,可放置图片、css 样式文件、js 脚本等,在模板文件中可通过如下方式引用:

    <head>
        <meta charset="utf-8">
        <title>纯CSS计时器演示</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="static/css/normalize.min.css">
        <link rel="stylesheet" href="static/css/style.css">
    </head>

    修改 index/views.py 中的 index 函数:

    def index(request):
        return render(request, 'index.html')  # 视图绑定模板

    作为学习和效果展示,可以选择直接使用一些成品效果的代码,例如,可以从纯CSS计时器演示获取源码并在当前项目进行效果重现。

    另外,Django 支持热修改,除配置文件之外,其他文件修改后,无需重启即可生效。

Django 基础命令

django-admin startproject [project_name] # 初始化一个项目
python3 manage.py startapp [app_name] # 在项目内创建页面子应用
python3 manage.py shell # 进入代码调试模式
python3 manage.py makemigrations # 数据库创建更改文件
python3 manage.py migrate # 同步到数据库进行更新
python3 manage.py flush # 清空数据库
python3 manage.py runserver 0.0.0.0:8000 # 启动服务

Django 模板

准备工作

  1. 使用虚拟环境创建项目 mydjango

  2. 创建应用 app

    - app
      - migrations
        __init__.py
      __init__.py
      admin.py # 
      apps.py # 
      models.py # 数据库连接文件
      tests.py # 测试文件
      views.py # 视图文件
      urls.py  # 子路由信息,通过手动创建

    注意:后文中会出现一些 mydjangoapp,并不是什么特殊名称,而是指当前项目和应用文件夹的名称,项目创建不同,自然就不同。

  3. 尝试启动项目,确保没有问题。

  4. 在 mydjango 项目下创建文件夹 templatesstatic 分别用于存放模板文件和静态资源。

  5. 修改 mydjango/mydjango/settings.py 配置。

    ALLOWED_HOSTS = ['*']
    
    INSTALLED_APPS = [
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'app',
    ]
    
    # 模板配置
    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'templates')],  # 指定模板文件位置:当前项目下的templates文件夹
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    
    # 指定静态文件位置
    STATICFILES_DIRS = [
        (os.path.join(BASE_DIR, 'static'))
    ]

路由与模板

在初始创建项目时,默认会指定如下路由:

# mydjango/mydjango/urls.py
from django.contrib import admin
from django.urls import path

urlpatterns = [
    path('admin/', admin.site.urls),
]

但在 app 目录下中也可以创建子路由,但其名称必须指定为 urls.py

# mydjango/app/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('index/', views.Index),
    path('data/', views.Data),
]

但是,如果要使子路由生效,必须在项目路由中进行指定:

# mydjango/mydjango/urls.py
from django.urls import path, include

urlpatterns = [
    path('', include('app.urls')),
]

这样,当浏览器访问 http://127.0.0.1:8000/index/ 时,就会通过项目路由转到子路由,并最终交由子路由中指定的视图解析器 views.Index 进行解析。

在进行视图解析时,我们可以有如下两种解析形式:

# mydjango/app/views.py
from django.http import HttpResponse
from django.shortcuts import render

# 形式一:返回数据
def Data(request):
    return HttpResponse('Hello')

# 形式二:返回渲染后的模板
def Index(request):
    data = {'title': 'My Resume!', 'author': 'Chinmoku'}
    return render(request, 'index.html', data)

当返回数据时,直接访问路由地址,即可获取到对应的数据。当返回视图模板时,会将参数传递到模板中,模板会解析参数,并补充到对应位置,此处使用到的模板如下:

{# mydjango/templates/index.html #}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>{.{ title }}</title>
</head>
<body>
<div>姓名:<span>{.{ author }}</span></div>
</body>
</html>

这种 {.{ value }} 的模板占位方式,被称为 Mustache 语法,这种语法应用其实很广,不仅是在 python 或 java 的一些模板引擎中用到,在一些前端场景中通常使用尤为广泛,如:Vue。

模板:列表

基本语法:

{.% for item in list %}
 	... item display
{.% endfor %}

视图文件返回数据列表:

# mydjango/app/views.py
from django.shortcuts import render

def Index(request):
    skills = ['java web', 'html/css/js', 'vue', 'wx mini-program', 'sql', 'python']
    data = {'title': 'My Resume!', 'author': 'Chinmoku', 'skills': skills}
    return render(request, 'index.html', data)

在模板中对列表进行遍历:

{# mydjango/templates/index.html #}
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <title>{.{ title }}</title>
</head>
<body>
<div>作者:<span>{.{ author }}</span></div>
<div>
    <p>我的技能:</p>
    <div style="text-indent: 2em;">
        {.% for skill in skills %}
        <p>{.{ forloop.counter }}. {.{ skill }}</p>
        {.% endfor %}
    </div>
    <p>主技能:{.{ skills.0 }}</p>
</div>
</body>
</html>

这就是遍历的基本使用方式,但在遍历过程中,django 也提供了一些方法帮助我们获取循环相关的信息:

  1. forloop.counter
  2. forloop.counter0:下标从 0 开始。
  3. forloop.revcounter
  4. forloop.revcounter0
  5. forloop.first
  6. forloop.last
  7. empty

这些信息是一眼就能看懂的,相信不用多做解释,要是一眼看不懂,就多看一眼,你会懂的。

模板:字典

# mydjango/app/views.py
from django.shortcuts import render


def Index(request):
    data = {
        'info': {'age': 1000, 'birthday': 'xxxx-xx-xx xx:xx:xx'}
    }
    return render(request, 'index.html', data)

使用:

{# mydjango/templates/index.html #}
<p>生日:{.{ info.birthday }}</p>
<p>年龄:{.{ info.age }}</p>

模板:过滤器

基本语法:{.{ var | filter_name: not_required_params }}

过滤器的使用方式大概是这样:

{# mydjango/templates/index.html #}
<p>年龄:{.{ info.age | add:18 }}</p>

按照这样的使用方式,就可以将原有的值,通过某种规则,返回一个被计算或处理后的值,例如这里的自增 18。

这种类似的被 Django 内置支持的过滤器还有不少,可以在网上看一看

根据万物皆可套娃原则,过滤器也可以这样使用:

{# mydjango/templates/index.html #}
<p>年龄:{.{ info.age | add:18 | divisibleby:5 | upper | linenumbers | length }}</p>

以上是 Django 内置的过滤器,但在业务过程中,有时也需要对过滤器进行自定义,这里提供一下自定义过滤器的流程:

  1. 在 app 应用下创建文件夹 templatetags(固定名称)。

  2. 在 templatetags 下创建文件,例如 myfilters.py(名称自定义)。

    # mydjango/app/templatetags/myfilters.py
    from django.template import Library
    
    register = Library()  # register名字固定不可变
    
    @register.filter(name='append')  # 不指定名字,就会使用函数名
    def years(value, param):
        print(value)  # 过滤之前的原值
        return value.__str__() + ' ' + param
  3. 在模板中进行使用。

    {# mydjango/templates/index.html #}
    {.% load myfilters %}
    
    {# 此处省略若干代码 #}
    
    <p>年龄:{.{ info.age | append:'year(s)' }}</p>

注意:自定义过滤器只支持两个参数,参数一是过滤前的原值,参数二是可选的过滤参数。

模板:条件判断

基本语法:

{.% if condition1 %}
   ... display 1
{.% elif condition2 %}
   ... display 2
{.% else %}
   ... display 3
{.% endif %}

使用示例:

<p>称呼:王
    {.% if info.age < 35 %} 叔叔
	{.% elif info.age < 80 %} 大爷
    {.% else %} 老妖怪
    {.% endif %}
</p>

条件判断中,也支持与、或、非的判断,分别使用 and,or,not,不提。

模板:ifequal

{.% ifequal user currentuser %}
	<h1>Welcome!</h1>
{.% endifequal %}

与之相反的是 ifnotequal

模板:标签

Django 模板内置标签:

  • {.% for %}{.% endfor %}:遍历输出内容。
  • {.% if %}{.% elif %}{.% endif %}:条件判断。
  • {.% url name args %}:引用路由配置名。
  • {.% load %}:加载 Django 标签库。
  • {.% load static %}
  • {.% static static_path %}:读取静态资源。
  • {.% extends base_template %}:模板继承。
  • {.% block data %}{.% endblock %}:重写父模板代码。
  • {.% csrf_token %}:跨域密钥。

静态资源

使用静态资源之前,需要在 settings.py 中进行配置:

# 指定静态文件位置
STATICFILES_DIRS = [
    (os.path.join(BASE_DIR, 'static'))
]

模板中使用:

{.%% load static files %}

<img src="{.% static 'images/test.jpg' %}" />

自定义标签

自定义标签步骤:

  1. 应用目录下创建 templatetags 目录。

  2. 在 templatetags 目录下创建任意文件,如 my_tags.py

  3. 配置文件中指定配置。

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'templates')],  # 指定模板文件位置:当前项目下的templates文件夹
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
                "libraries":{
                    'my_tags': 'app.templatetags.my_tags'
                }
            }
        },
    ]
  4. 编辑 my_tags.py

    # mydjango/app/templatetags/my_tags.py
    from django import template
    
    register = template.Library()
    
    @register.simple_tag(name='mytag')
    def my_tag1(v1, v2, v3):
        return v1 * v2 * v3
  5. 在模板文件中使用

    {# mydjango/templates/index.html #}
    {.% load my_tags %}
    <p>11 * 22 * 33 = {.% mytag 11 22 33 %}</p>

此外,自定义标签时,也可以对标签内容进行语义化处理:

# mydjango/app/templatetags/my_tags.py
from django import template
from django.utils.safestring import mark_safe

register = template.Library()

@register.simple_tag
def my_html(v1, v2):
    temp_html = "<input type='text' id='%s' placeholder='%s' />" % (v1, v2)
    return mark_safe(temp_html)

在模板文件中使用:

<!--mydjango/templates/index.html-->
{.% load my_tags %}
<div>{.% my_html 20 '请输入年龄' %}</div>

其他模板知识

  1. 注释

    {# 这是一段注释信息 #}
  2. include 标签

    include 标签允许在模板之中包含其他模板:

    {.% include "nav.html" %}
  3. csrf_token

    csrf_token 用于 form 表单中,作用是跨站请求伪造保护,使用这个标签,表单提交数据才会成功,否则进行再次跳转页面时会抛出 403 错误。

  4. 其他模板如 jinja2mako 等,了解即可,如有需求,自行摸索。

Django 模型

Django 对各种数据库提供了很好的支持,包括:PostgreSQL、MySQL、SQLite、Oracle,本文将以 MySQL 作为示例进行学习。

安装 python mysql 驱动:

pip3 install pymysql -i https://pypi.douban.com/simple

数据库配置

手动创建数据库:

create database mydjango default charset=utf8;

配置数据库信息:

# mydjango/mydjango/settings.py
DATABASES = {
    'default':
    {
        'ENGINE': 'django.db.backends.mysql',  # 数据库引擎
        'NAME': 'mydjango',  # 数据库名称
        'HOST': '127.0.0.1',  # 数据库地址,本机 ip 地址 127.0.0.1
        'PORT': 3306,  # 端口
        'USER': 'root',   # 数据库用户名
        'PASSWORD': '123456',  # 数据库密码
    }
}

settings.py 同级目录下的 __init__.py 中编辑内容:

# mydjango/mydjango/__init__.py
import pymysql

pymysql.install_as_MySQLdb()

定义模型

编辑模型文件:

# mydjango/app/models.py
from django.db import models

class Test(models.Model):
    name = models.CharField(max_length=20)

创建表结构:

python3 manage.py migrate # 创建表结构
python3 manage.py makemigrations
python3 manage.py migrate

执行成功后,就会自动向数据库创建一张表 app_test(表名与当前模块和模型内的类名有关)。

提示:

  1. 如需在 PyCharm Terminal 中自动激活虚拟环境,可在 settings -> Tools -> Terminal -> Shell path 中进行类似如下的配置:

    "cmd.exe" /k ""A:\python-venv\django_venv\Scripts\activate""

  2. 执行 python3 manage.py migrate 时报错:‘str’ object has no attribute ‘decode’

    点击此处前往查看解决方法。

数据库操作

配置路由:

# mydjango/app/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('user/add/<str:name>', views.UserAdd),
]

视图处理:

# mydjango/app/views.py
from django.http import HttpResponse
from app.models import Test

def UserAdd(request, name):
    test1 = Test(name=name)
    test1.save()
    return HttpResponse("<p>数据添加成功!</p>")

浏览器访问 http://127.0.0.1:8000/user/add/zhang 即可向数据库添加一条记录,且其主键默认采用自增策略。

类似地,通过对象直接调用方法,即可完成对数据库的增删改查:

# 查询数据
Test.objects.all()  # 查询所有,相当于 => select *
Test.objects.filter(id=1)  # 过滤查询,相当于 => where
Test.objects.get(id=1)  # 查询单个对象
Test.objects.order_by('name')[0:2]  # 排序并限制,相当于 => order by 'name' offset 0 limit 2
Test.objects.order_by("id")  # 排序
Test.objects.filter(name="runoob").order_by("id")  # 连锁使用
# 更新数据
test1 = Test.objects.get(id=1)
test1.name = 'Ouyang'
test1.save()
Test.objects.filter(id=1).update(name='Ouyang')
Test.objects.all().update(name='Xxx')  # 修改所有
# 删除数据
test2 = Test.objects.get(id=1)
test2.delete()
Test.objects.filter(id=1).delete()
Test.objects.all().delete()  # 删除所有

Django 表单

GET

创建模板文件:

<!--mydjango/templates/search_form.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Django form get</title>
</head>
<body>
<form action="/search/" method="get">
    <input type="text" name="q" placeholder="请输入搜索内容">
    <input type="submit" value="搜索">
</form>
</body>
</html>

编辑视图文件:

# mydjango/app/views.py
from django.http import HttpResponse
from django.shortcuts import render

# 表单
def search_form(request):
    return render(request, 'search_form.html')

# 接收请求数据
def search(request):
    request.encoding = 'utf-8'
    if 'q' in request.GET and request.GET['q']:
        message = '你搜索的内容为: ' + request.GET['q']
    else:
        message = '你提交了空表单'
    return HttpResponse(message)

配置路由:

# mydjango/app/urls.py
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^search-form/$', views.search_form),
    url(r'^search/$', views.search),
]

Post

创建模板文件:

{# mydjango/templates/post.html #}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Django form post</title>
</head>
<body>
<form action="/search-post/" method="post">
    {.% csrf_token %}{# 跨域密钥 #}
    <input type="text" name="q" placeholder="请输入搜索内容">
    <input type="submit" value="搜索">
</form>
<p>{.{ keyword }}</p>
</body>
</html>

编辑视图文件:

# mydjango/app/views.py
from django.shortcuts import render

def search_post(request):
    ctx = {}
    if request.POST:
        ctx['keyword'] = request.POST['q']
    return render(request, "post.html", ctx)

配置路由:

# mydjango/app/urls.py
from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^search-post/$', views.search_post),
]

通过这种方式,可以将提交的数据处理后,重新渲染到页面上。

Request 对象

每个视图函数的第一个参数都必须是 HttpRequest 对象,通过该对象,可以获取一些列属性:

from django.http import HttpResponse

def dj_test(request):
    print(request.path)
    return HttpResponse("Hello world!")

request 中的属性主要有:

  1. path
  2. method
  3. GET
  4. POST
  5. REQUEST:为了方便,该属性是POST和GET属性的集合体,但是有特殊性,先查找POST属性,然后再查找GET属性。(不建议使用)
  6. COOKIES
  7. FILES
  8. META:包含所有可用HTTP头部信息的字典。
  9. user:是一个 django.contrib.auth.models.User 对象,代表当前登录的用户。可以通过 user 的 is_authenticated() 方法来辨别用户是否登录。
  10. session
  11. raw_post_data:原始 HTTP POST 数据,未解析过。高级处理时会有用处。

request 也提供了一些方法:

  1. __getitem__(key):返回 GET/POST 的键值,先取 POST,后取 GET。如果键不存在则抛出 KeyError。
  2. has_key():检查 request.GET or request.POST 中是否包含参数指定的 Key。
  3. get_full_path()
  4. is_secure():检验 HTTPS。

QueryDict 对象

在 HttpRequest 对象中, GET 和 POST 属性是 django.http.QueryDict 类的实例。QueryDict 类似字典的自定义类,用来处理单键对应多值的情况,它不仅实现所有标准的词典方法,还包括了一些特有的方法:

  1. __getitem__(key):和标准字典的处理有一点不同,就是,如果 Key 对应多个 Value,则会返回最后一个 value。

  2. __setitem__(key, value):这里的 value 是一个 list,它只能在一个 mutable QueryDict 对象上被调用(即通过 copy 产生的一个 QueryDict 对象的拷贝)。

  3. get():如果对应多个 value,则返回最后一个。

  4. update():参数可以是 QueryDict,也可以是标准字典。和标准字典的update方法不同,该方法添加字典 items,而不是替换它们。

    q = QueryDict('a=1')
    q = q.copy() # to make it mutable
    q.update({'a': '2'})
    print(q.getlist('a'))  # ['1', '2']
    print(q['a'])  # ['2'] # return the last one
  5. items():和标准字典同名方法有一点不同,该方法使用单值逻辑的 _getitem_()。

    q = QueryDict('a=1&a=2&a=3')
    print(q.items())  # [('a', '3')]
  6. values():同样是单值。

  7. copy

  8. getlist

  9. setlist

  10. appendlist

  11. setlistdefault

  12. lists

  13. urlencode

Django 视图

一个视图函数,简称视图,是一个简单的 Python 函数,它接受 Web 请求并且返回 Web 响应。响应可以是一个 HTML 页面、一个 404 错误页面、重定向页面、XML 文档、或者一张图片等。视图逻辑一般放在项目的 views.py 文件中,但并不限制。

视图响应方式包括:HttpResponse、render、redirect。

HttpResponse

返回字符串文本,包括 html 形式的字符串在内。

from django.http import HttpResponse

def http_response_text(request):
    return HttpResponse('Hello world!')

def http_response_html(request):
    return HttpResponse('<a href="https://www.chinmoku.cc">チンモクのブログ</>')

HttpResponse 返回 html 代码时,代码内容由模板进行渲染处理。

render

返回文本,第二个参数为模板页面名称,第三个参数为字典(可选)。

from django.shortcuts import render

def Home(request, age, name):
    data = {'name': name, 'age': 'age'}
    return render(request, "home.html", data)

render 底层返回的也是 HttpResponse 对象。

redirect

重定向,跳转新页面。参数为字符串,字符串中填写页面路径。一般用于 form 表单提交后,跳转到新页面。

from django.shortcuts import redirect

def to_index(request):
    return redirect('/index/')

redirect 底层继承自 HttpResponse。

JsonResponse

JsonResponse 返回标准 JSON,与 HttpResponse 不同的是,JsonResponse 无需手动进行序列化与反序列化。HttpResponse 字符串 JSON 则需要对序列化和反序列化手动进行处理。

Django 路由

路由简单的来说就是根据用户请求的 URL 链接来判断对应的处理程序,并返回处理结果,也就是 URL 与 Django 的视图建立映射关系。Django 路由在 urls.py 配置,urls.py 中的每一条配置对应相应的处理方法。

路由配置方式

  1. Django 1.1.x

    from django.conf.urls import url # 用 url 需要引入
    
    urlpatterns = [
        url(r'^admin/$', admin.site.urls),
        url(r'^index/$', views.Index), # 普通路径
        url(r'^articles/([0-9]{4})/$', views.Articles), # 正则路径
    ]
  2. Django 2.2.x+

    from django.urls import re_path # 用re_path 需要引入
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('index/', views.index), # 普通路径
        re_path(r'^articles/([0-9]{4})/$', views.articles), # 正则路径(版本向下兼容,也可以使用url())
    ]
    • path:用于普通路径,不需要自己手动添加正则首位限制符号,底层已经添加。
    • re_path:用于正则路径,需要自己手动添加正则首位限制符号。

正则路由

  1. 正则路径中的无名分组

    # mydjango/app/urls.py
    from django.conf.urls import re_path
    from . import views
    
    urlpatterns = [
        path('home/<str:name>/<int:age>', views.Home),
        re_path(r"^index/(?P<age>[0-9]{2})/(?P<name>[A-Z]{2})/", views.Home),
    ]

    视图处理:

    from django.shortcuts import render
    from django.shortcuts import HttpResponse
    
    def Articles(request, year):
        print(year)  # 一个形参代表路径中一个分组的内容,按顺序匹配
        articleId = request.GET.get('articleId', '')  # 获取非路径参数的get参数
        print(articleId)
        # ...处理逻辑
        return HttpResponse('Django tutorial!')
  2. 正则路径中的有名分组

    # mydjango/app/urls.py
    from django.conf.urls import re_path
    from . import views
    
    urlpatterns = [
        path('home/<str:name>/<int:age>', views.Home),
        re_path(r"^home2/(?P<age>[0-9]{2})/(?P<name>[A-Z]{2})/", views.Home),
    ]

    视图处理:

    from django.shortcuts import render
    from django.shortcuts import HttpResponse
    
    def Home(request, age, name):
        data = {'name': name, 'age': age}
        return render(request, "home.html", data)

    有名分组会在路由中指定参数名称,视图解析时则不需要按照规定顺序,只需要根据名称进行匹配即可。

  3. 路由分发

    Django 项目中多个 app 共用一个 urls.py 容易造成混淆,因此,每个 app 可以拥有自己的 urls.py 文件配置路由。

    而在项目路由中,只需要通过如下方式配置路由分发即可:

    from django.urls import path, include
    
    urlpatterns = [
        path('', include('app.urls')),
    ]

反向解析

在路由配置中,指定路由的 name 属性,即可设置反向解析的参考点,例如:

# mydjango/app/urls.py
from django.conf.urls import url
from django.urls import path
from . import views


urlpatterns = [
    path('index/', views.Index),
    path('search-form/', views.Index, name='toForm'),
]

在模板中使用:

<!-- mydjango/templates/index.html -->

<div><a href="{.% url 'toForm' %}">填写信息</a></div>

点击链接即可将请求发送到 views.Index 进行处理。

在视图中使用:

# mydjango/app/views.py
from django.shortcuts import render, redirect

def redirectForm(request):
    return redirect(reversed('toForm'))

当某一请求调用执行 redirectForm 时,就会重定向到 views.Index 进行后续处理。

在反向解析路由时,同样可以传递参数,包括有名和无名分组在内,传递参数方式如下:

{# mydjango/templates/index.html #}

<div><a href="{.% url 'toForm' 19 %}">填写信息</a></div>
<div><a href="{.% url 'toForm' age=19 %}">填写信息</a></div>

在视图中反向解析时传递参数:

# mydjango/app/views.py
from django.shortcuts import render, redirect

def redirectForm(request):
    return redirect(reversed('toForm', args=(19,)))  # 无名分组
    # return redirect(reversed('toForm', kwargs={'age': 19}))  # 无名分组

路由别名 name 没有作用域,Django 在反向解析 URL 时,会在项目全局顺序搜索,当查找到第一个路由别名 name 指定 URL 时,立即返回。当在不同的 app 目录下的urls 中定义相同的路由别名 name 时,可能会导致 URL 反向解析错误。

命名空间

命名空间是表示标识符的可见范围。

  • 一个标识符可在多个命名空间中定义,它在不同命名空间中的含义是互不相干的。
  • 一个新的命名空间中可定义任何标识符,它们不会与任何重复的标识符发生冲突,因为重复的定义都处于其它命名空间中。

由于路由别名没有作用于,因此多个 app 存在同名别名时,会导致反向解析错误,但如果对其限定命名空间,就可以避免该错误。

# mydjango/mydjango/urls.py
from django.urls import path, include

urlpatterns = [
    path('', include(('app.urls', 'app'))),
]

在子路由中指定反向解析别名:

# mydjango/app/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('search-form/', views.Index, name='toForm'),
]

此时使用如下方式进行路由反向解析,则只会在命名空间指定的 app 下查找路由:

# mydjango/app/views.py
from django.shortcuts import render, redirect

def redirectForm(request):
    return redirect(reversed('app:toForm'))

在模板中的使用方式也是类似:

{# mydjango/templates/index.html #}

<div><a href="{.% url 'app:toForm' 19 %}">填写信息</a></div>

Django Admin

配置

Django 提供了基于 web 的管理工具。Django 自动管理工具是 django.contrib 的一部分。Django Admin 默认情况下即被注册启用:

# mydjango/mydjango/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

如需使用,只需在路由中进行指定即可(通常创建项目时默认即指定了):

# mydjango/mydjango/urls.py
from django.conf.urls import url
from django.contrib import admin

urlpatterns = [
    url(r'^admin/', admin.site.urls),
]

通过访问 http://127.0.0.1:8000/admin/ 即可跳转登录界面。

登录与使用

可以通过命令 python3 manage.py createsuperuser 来创建超级用户,创建成功后,即可登录,登录成功后将跳转如下页面:

image-20211027232541350

如需在 admin 界面管理某个数据模型,只需要在应用下的 admin.py 文件中注册对应的数据模型即可:

from django.contrib import admin
from app.models import Test

# Register your models here.
admin.site.register(Test)

然后刷新 admin 界面,即可看到对应的数据模型。

自定义表单

自行摸索吧,感觉没啥意思。

Django ORM

单表实例

  1. 准备工作

    注册当前应用:

    # mydjango/mydjango/settings.py
    INSTALLED_APPS = (
        'django.contrib.admin',
        'django.contrib.auth',
        'django.contrib.contenttypes',
        'django.contrib.sessions',
        'django.contrib.messages',
        'django.contrib.staticfiles',
        'app01',  # 添加此项
    )

    指定数据库连接:

    # mydjango/mydjango/__init__.py
    import pymysql
    pymysql.install_as_MySQLdb()
  2. 创建模型

    # mydjango/app/models.py
    from django.db import models
    
    class Book(models.Model):
        id = models.AutoField(primary_key=True)  # id 会自动创建,可以手动写入
        title = models.CharField(max_length=32)  # 书籍名称
        price = models.DecimalField(max_digits=5, decimal_places=2)  # 书籍价格
        publish = models.CharField(max_length=32)  # 出版社名称
        pub_date = models.DateField()  # 出版时间

    执行数据迁移命令:

    python3 manage.py migrate  # 创建表结构
    python3 manage.py makemigrations app  # 让 Django 知道我们在我们的模型有一些变更
    python3 manage.py migrate app  # 创建表结构

    执行完毕后,数据库会生成一张 app_book 表。

  3. 新增数据

    编写新增数据的视图函数:

    # mydjango/app/views.py
    from django.http import HttpResponse
    from app import models
    
    def add_book(request):
        book = models.Book(title="捕鱼女", price=35.00, publish="江苏凤凰文艺出版社", pub_date="2015-10-01")
        book.save()
        return HttpResponse('<p><script>alert("数据添加成功!")</script></p>')

    配置路由规则:

    # mydjango/app/urls.py
    from django.urls import path
    from . import views
    
    urlpatterns = [
        path('book/add/', views.add_book),
    ]

    浏览器访问 http://127.0.0.1:8000 即可在向数据库中新增一条记录。

    此外,也可以使用如下方式新增数据:

    books = models.Book.objects.create(title="日本文学史", price=48.00, publish="译林出版社", pub_date="2020-06-01")
    print(books, type(books))  # Book object (4)
  4. 查询数据

    • 查询列表

      def get_books(request):
          books = models.Book.objects.all()
          print(books, type(books))  # QuerySet类型
          return HttpResponse('<p><script>alert("数据列表查询成功!")</script></p>')
    • 过滤查询

      def get_book(request, book_id):
          books = models.Book.objects.filter(pk=book_id)  # 这里pk相当于primary key,因为id有其他特殊意义所以使用pk替代id
          # books = models.Book.objects.filter(price__lt=40.00)
          # books = models.Book.objects.filter(title__iendswith='文学史')
          print(books, type(books))  # QuerySet类型
          for book in books:
              print(book.title)
          return HttpResponse('<p><script>alert("数据列表过滤查询成功!")</script></p>')

      filter 也内置了一些其他常用的筛选方式,通过如下提示即可查看:

      image-20211101204130217

      关于过滤规则及其意义,可以点击此处作为参考,这里不反复书写示例。

      filter 中只能使用 =,无法使用大于小于等其他比较符号。但是,可以使用 __gt 这样的方式来间接表示。

    • 反向查询

      # 查询不符合条件(pk=book_id)的所有数据
      books = models.Book.objects.exclude(pk=book_id)
    • 单条查询

      book = models.Book.objects.get(pk=book_id)

      单条查询的查询条件不一定是主键,但必须保证查询结果是单一的,否则会抛出异常。

    • 排序

      books = models.Book.objects.filter(price__lt=40.00).order_by('title')  # 升序
      # books = models.Book.objects.filter(price__lt=40.00).order_by('-title')  # 降序(在条件前添加负号“-”即可)
    • 排序翻转

      books = models.Book.objects.filter(price__lt=40.00).order_by('title').reverse()  # 将原本的排序进行翻转
    • 计数

      count = models.Book.objects.filter(price__gt=40.00).count()
      print(count, type(count))  # int类型
    • 查询第一条

      books = models.Book.objects.first() # 返回所有数据的第一条数据

      其实就是在所有查询的数据列表中,取下标为 0 的数据。

    • 查询最后一条(同上)

    • 仅查询指定的字段

      book_vals = models.Book.objects.values('pk', 'title')
      print(book_vals)  # QuerySet内部是可迭代的字典序列
      book_val_list = models.Book.objects.values_list('pk', 'title')
      print(book_val_list)  # QuerySet内部是元组
    • 去重复

      books = models.Book.objects.values_list("publish").distinct()
  5. 修改数据

    def update_book(request, book_id):
        book = models.Book.objects.get(pk=book_id)
        book.price = 40.00
        book.save()
        # books = models.Book.objects.filter(pk__in=[7, 8]).update(price=35.00)
        return HttpResponse('<p><script>alert("数据更新成功!")</script></p>')
  6. 删除数据

    books=models.Book.objects.filter(pk=5).delete()
    # books = models.Book.objects.all().delete()  # 删除所有

    注意:数据删除属于非常规操作,在业务过程中,一般删除都进行逻辑删除,而非物理删除。

多表实例

表与表之间的关系,通常有如下三种:

  1. 一对一。
  2. 一对多。
  3. 多对多。

注意:这种对应关系时相对而言的,若甲与乙是一对多关系,那么,乙与甲则是一对一关系(也可以说是多对一),反之,则不一定成立。

  1. 准备工作

    • 移除上一实例生成的数据库表,以及删除对应的数据模型,或者重新新建应用。

    • 清除 mydjango/app/migrations 下的 sql 脚本文件。

    • 删除数据库表 django_migrations 中旧有生成记录。

      delete from django_migrations where app = 'app'
  2. 创建模型

    # mydjango/app/models.py
    from django.db import models
    
    class Book(models.Model):
        title = models.CharField(max_length=32)
        price = models.DecimalField(max_digits=5, decimal_places=2)
        pub_date = models.DateField()
        publish = models.ForeignKey("Publish", on_delete=models.CASCADE)
        authors = models.ManyToManyField("Author")
    
    class Publish(models.Model):
        name = models.CharField(max_length=32)
        city = models.CharField(max_length=64)
        email = models.EmailField()
    
    class Author(models.Model):
        name = models.CharField(max_length=32)
        age = models.SmallIntegerField()
        au_detail = models.OneToOneField("AuthorDetail", on_delete=models.CASCADE)
    
    class AuthorDetail(models.Model):
        gender_choices = (
            (0, "女"),
            (1, "男"),
            (2, "未知"),
        )
        gender = models.SmallIntegerField(choices=gender_choices)
        phone = models.CharField(max_length=32)
        address = models.CharField(max_length=64)
        birthday = models.DateField()

    说明:

    1. EmailField 数据类型是邮箱格式,底层继承 CharField,进行了封装,相当于 MySQL 中的 varchar。
    2. Django1.1 版本不需要联级删除:on_delete=models.CASCADE,Django2.2 需要。

    执行数据迁移命令:

    python3 manage.py migrate
    python3 manage.py makemigrations app
    python3 manage.py migrate app

    执行完毕后,数据库会生成如下表结构:

    image-20211102081211144

  3. 初始化数据

    为方便后续操作,为当前生成的表结构初始化部分数据:

    insert into app_publish(name, city, email) values ("山东文艺出版社", "山东", "602666251@qq.com"), ("商务印书馆", "北京", "bainianziyuan@cp.com.cn"), ("时代文艺出版社", "吉林", "367036063@qq.com"), ("花城出版社", "广东", "gdhctsyx@126.com");
    
    insert into app_book(title, price, pub_date, publish_id) values('深处的镜子', 42.00, '2018-08-01', 1), ('诗学', 36.00, '2009-07-01', 2), ('神殿的基石', 38.00, '2014-04-01', 4);
    
    insert into app_authordetail(gender, phone, address, birthday) values (1, 13432335433, "罗马尼亚", "1895-5-23"), (1, 13943454554, "古希腊", "1000-8-13");
    
    insert into app_author(name, age, au_detail_id) values ("卢齐安·布拉加", 60, 1), ("亚里士多德", 58, 2);
    
    insert into app_book_authors(book_id, author_id) values (1, 1), (2, 2);

    注意:由于表之间存在外键关联关系,因此,在手动插入数据时,需要遵循关联顺序进行插入。

  4. 新增数据

    一对多添加数据:

    def addBook(request):
        pub_obj = models.Publish.objects.filter(pk=3).first()
        book = models.Book.objects.create(title="一九八四", price=32.00, pub_date="2021-01-01", publish=pub_obj)
        # book = models.Book.objects.create(title="先知·沙与沫", price=35.00, pub_date="2018-07-01", publish_id=pub_obj.pk)  # 方法二
        print(book, type(book))
        return HttpResponse(book)

    多对多添加数据:

    def addBook(request):
        #  获取书籍对象
        book1 = models.Book.objects.filter(title="神殿的基石").first()
        book2 = models.Book.objects.filter(title="深处的镜子").first()
        #  获取作者对象
        author = models.Author.objects.filter(name="卢齐安·布拉加").first()
        #  给作者对象的 book_set 属性用 add 方法传书籍对象
        author.book_set.add(book1, book2)
        # author.book_set.add(book1.pk)  # 方法二
        return HttpResponse(author)

    其他用法:

    book_obj = models.Book.objects.get(id=10)
    author_list = models.Author.objects.filter(id__gt=2)
    book_obj.authors.add(*author_list)  # 将 id 大于2的作者对象添加到这本书的作者集合中
    # 方式二:传对象 id
    book_obj.authors.add(*[1,3]) # 将 id=1 和 id=3 的作者对象添加到这本书的作者集合中
    
    pub = models.Publish.objects.filter(name="商务印书馆").first()
    wo = models.Author.objects.filter(name="申忠信").first()
    book = wo.book_set.create(title="诗韵词韵速查手册", price=38.00, pub_date="2014-07-01", publish=pub)
    
    author_obj =models.Author.objects.get(id=1)
    book_obj = models.Book.objects.get(id=11)
    author_obj.book_set.remove(book_obj)  # 移除指定关联
    
    book = models.Book.objects.filter(title="菜鸟教程").first()
    book.authors.clear()  # 移除所有关联
  5. 查询数据

    # 查询某书籍出版社位置
    book = models.Book.objects.filter(pk=10).first()
    res = book.publish.city
    print(res, type(res))
    
    # 查询某出版社的出版记录
    pub = models.Publish.objects.filter(name="人民文学出版社").first()
    res = pub.book_set.all()
    for i in res:
        print(i.title)
    
    # 查询某作者联系方式
    author = models.Author.objects.filter(name="贾平凹").first()
    res = author.au_detail.phone
    print(res, type(res))
    
    # 查询成都作者
    addr = models.AuthorDetail.objects.filter(addr="成都").first()
    res = addr.author.name
    print(res, type(res))
    
    # 查询某书籍的联名作者
    book = models.Book.objects.filter(title="脂砚斋重评石头记").first()
    res = book.authors.all()
    for i in res:
        print(i.name, i.au_detail.phone)
    
    # 查询某作者的所有书籍
    author = models.Author.objects.filter(name="威廉·萨默塞特·毛姆").first()
    res = author.book_set.all()
    for i in res:
        print(i.title)
  6. 双下划线跨表查询

    # 查询某出版社书籍及价格
    res = models.Book.objects.filter(publish__name="凤凰文艺出版社").values_list("title", "price")
    res = models.Publish.objects.filter(name="凤凰文艺出版社").values_list("book__title","book__price")
    
    # 查询某作者所有书籍名称
    res = models.Book.objects.filter(authors__name="张爱玲").values_list("title")
    res = models.Author.objects.filter(name="张爱玲").values_list("book__title")
    
    # 查询某作者联系方式
    res = models.Author.objects.filter(name="汪曾祺").values_list("au_detail__phone")
    res = models.AuthorDetail.objects.filter(author__name="汪曾祺").values_list("phone")

Django ORM 主要思想在于通过操作对象来实现对数据库关联表的增删改查,其实这种通过对象实体之间的关系来操作数据库关联表的技术(或框架)并不鲜见,例如 Java 中的 Spring Data JPA,理解了对象操作的本质,自然一通百通,而不需要强行记忆。

Django Form 组件

Django Form 组件用于对页面进行初始化,生成 HTML 标签,此外还可以对用户提交对数据进行校验(显示错误信息)。

使用方式

  1. 创建 Form 组件文件。

    # mydjango/app/My_forms.py
    from django import forms
    from django.core.exceptions import ValidationError
    from app import models
    
    class EmpForm(forms.Form):
        name = forms.CharField(min_length=4, label="姓名", error_messages={"min_length": "至少需要4个字符", "required": "该字段不能为空!"})
        age = forms.IntegerField(label="年龄")
        salary = forms.DecimalField(label="工资")
  2. 视图处理逻辑

    # mydjango/app/views.py
    from django.http import HttpResponse
    from django.shortcuts import render, redirect
    
    from app import models
    from app.My_forms import EmpForm
    
    def add_emp(request):
        if request.method == "GET":
            form = EmpForm()
            return render(request, "add_emp.html", {"form": form})
        else:
            form = EmpForm(request.POST)
            if form.is_valid():  # 进行数据校验
                # 校验成功
                data = form.cleaned_data  # 校验成功的值,会放在cleaned_data里。
                data.pop('r_salary')
                print(data)
    
                models.Emp.objects.create(**data)
                return HttpResponse('ok')
                # return render(request, "add_emp.html", {"form": form})
            else:
                print(form.errors)    # 打印错误信息
                clean_errors = form.errors
            return render(request, "add_emp.html", {"form": form, "clean_errors": clean_errors})

    配置路由:

    path('add_emp/', views.add_emp)
  3. 新建模板文件

    <!-- mydjango/templates/add_emp.html-->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
    
    <h3>添加员工</h3>
    
    <!-- 1、自己手动写HTML页面-->
    <form action="" method="post">
        <p>姓名:<input type="text" name="name"></p>
        <p>年龄:<input type="text" name="age"></p>
        <p>工资:<input type="text" name="salary"></p>
        {.% csrf_token %}
        <input type="submit">
        {.{ clean_errors }}
    </form>
    
    <!-- 2、通过form对象的as_p方法实现-->
    <form action="" method="post" novalidate>
        {.% csrf_token %}
        {.{ form.as_p }}
        <input type="submit">
    </form>
    
    <!-- 3、手动获取form对象的字段-->
    <form action="" method="post" novalidate>
        {.% csrf_token %}
        <div>
            <label for="id_{.{ form.name.name }}">姓名</label>
            {.{ form.name }} <span>{.{ form.name.errors.0 }}</span>
        </div>
        <div>
            <label for="id_{.{ form.age.name }}">年龄</label>
            {.{ form.age }} <span>{.{ form.age.errors.0 }}</span>
        </div>
        <div>
            <label for="id_salary">工资</label>
            {.{ form.salary }} <span>{.{ form.salary.errors.0 }}</span>
        </div>
        <input type="submit">
    </form>
    
    
    <!-- 4、用for循环展示所有字段 -->
    <form action="" method="post" novalidate>
        {.% csrf_token %}
        {.% for field in form %}
            <div>
                <label for="id_{.{ field.name }}">{.{ field.label }}</label>
                {.{ field }} <span>{.{ field.errors.0 }}</span>
            </div>
        {.% endfor %}
        <input type="submit">
    </form>
    
    </body>
    </html>

局部钩子和全局钩子

表单组件:

# mydjango/app/My_forms.py
from django import forms
from django.core.exceptions import ValidationError
from app import models

class EmpForm(forms.Form):
    name = forms.CharField(min_length=5, label="姓名", error_messages={"required": "该字段不能为空!", "min_length": "用户名太短。"})
    age = forms.IntegerField(label="年龄")
    salary = forms.DecimalField(max_digits=5, decimal_places=2, label="工资")
    r_salary = forms.DecimalField(max_digits=5, decimal_places=2, label="请再输入工资")

    def clean_name(self):  # 局部钩子
        val = self.cleaned_data.get("name")
        if val.isdigit():
            raise ValidationError("用户名不能是纯数字")
        elif models.Emp.objects.filter(name=val):
            raise ValidationError("用户名已存在!")
        else:
            return val

    def clean(self):  # 全局钩子 确认两次输入的工资是否一致。
        val = self.cleaned_data.get("salary")
        r_val = self.cleaned_data.get("r_salary")


        if val == r_val:
            return self.cleaned_data
        else:
            raise ValidationError("请确认工资是否一致。")

视图处理:

def add_emp(request):
    if request.method == "GET":
        form = EmpForm()  # 初始化form对象
        return render(request, "add_emp.html", {"form":form})
    else:
        form = EmpForm(request.POST)  # 将数据传给form对象
        if form.is_valid():  # 进行校验
            data = form.cleaned_data
            data.pop("r_salary")
            models.Emp.objects.create(**data)
            return redirect("/index/")
        else:  # 校验失败
            clear_errors = form.errors.get("__all__")  # 获取全局钩子错误信息
            return render(request, "add_emp.html", {"form": form, "clear_errors": clear_errors})

模板:

<form action="" method="post" novalidate>
    {.% csrf_token %}
    <div>
        <label for="id_{.{ form.name.name }}">姓名</label>
        {.{ form.name }} <span>{.{ form.name.errors.0 }}</span>
    </div>
    <div>
        <label for="id_{.{ form.age.name }}">年龄</label>
        {.{ form.age }} <span>{.{ form.age.errors.0 }}</span>
    </div>
    <div>
        <label for="id_salary">工资</label>
        {.{ form.salary }} <span>{.{ form.salary.errors.0 }}{.{ clear_errors.0 }}</span>
    </div>
    <div>
        <label for="id_r_salary">请再输入工资</label>
        {.{ form.r_salary }} <span>{.{ form.r_salary.errors.0 }}{.{ clear_errors.0 }}</span>
    </div>
    <input type="submit">
</form>

注:这一部分的代码示例基本是从Django Form 组件 | 菜鸟教程直接拷贝过来的。(目前的 WEB 应用基本都是前后端分离的,就实际而言,这一小节内容的价值并不大,因此,对于这一部分的代码也未做实际的操作验证)

Django Auth

Django 用户认证组件需要导入 auth 模块:

# 认证模块
from django.contrib import auth
# 对应数据库
from django.contrib.auth.models import User

用户级别

可以通过如下方法创建用户对象:

  1. create() 创建一个普通用户,密码是明文的。

    User.objects.create(username='test', password='123456')
  2. create_user() 创建一个普通用户,密码是密文的。

    User.objects.create_user(username='zhangsan', password='123456')
  3. create_superuser 创建一个超级用户,密码是密文的,要多传一个邮箱 email 参数。

    User.objects.create_superuser(username='chinmoku', password='123456', email='chinmoku@xxx.com')

用户验证

from django.contrib import auth

def login(request):
    if request.method == "GET":
        return render(request, "login.html")
    username = request.POST.get("username")
    password = request.POST.get("pwd")
    valid_num = request.POST.get("valid_num")
    keep_str = request.session.get("keep_str")
    if keep_str.upper() == valid_num.upper():
        user_obj = auth.authenticate(username=username, password=password)
        print(user_obj.username)
        if not user_obj:
            return redirect("/login/")
        else:
            auth.login(request, user_obj)
            path = request.GET.get("next") or "/index/"
            print(path)
            return redirect(path)
    else:
        return redirect("/login/")

如果用户账号密码验证通过,则返回该用户对象,否则,返回 None。

用户注销

from django.contrib import auth

def logout(request):
    _out = auth.logout(request)
    print(_out) # None
    return redirect("/login/")

登录重定向

可以通过装饰器,指定某页面需要登录才能访问:

from django.contrib.auth.decorators import login_required

@login_required
def index(request):
    data = {}
    return render(request, 'index.html', data)

此时访问 http://127.0.0.1:8000/index/ ,如果用户未登录,则会被重定向到 http://127.0.0.1:8000/login/?next=/index/ 页面。

当用户登录时,可以通过如下方式进行处理,使其登录成功后重定向到 http://127.0.0.1:8000/index/ 页面:

path = request.GET.get("next") or "/index/"
return redirect(path)

Cookies & Session

Django Cookies 操作

Cookies 在 Django 中的简单应用如下:

def login(request):
    if request.method == "GET":
        return render(request, "login.html")
    username = request.POST.get("username")
    password = request.POST.get("pwd")

    user_obj = models.UserInfo.objects.filter(username=username, password=password).first()
    print(user_obj.username)

    if not user_obj:
        return redirect("/login/")
    else:
        rep = redirect("/index/")
        rep.set_cookie("is_login", True)  # 设置Cookies
        return rep
       
def index(request):
    print(request.COOKIES.get('is_login'))
    status = request.COOKIES.get('is_login')  # 获取Cookies
    if not status:
        return redirect('/login/')
    return render(request, "index.html")

def logout(request):
    rep = redirect('/login/')
    rep.delete_cookie("is_login")  # 删除Cookies
    return rep
   
def order(request):
    print(request.COOKIES.get('is_login'))
    status = request.COOKIES.get('is_login')  # 获取Cookies
    if not status:
        return redirect('/login/')
    return render(request, "order.html")

Django Session 操作

Session 在 Django 中的简单应用如下:

def login(request):
    if request.method == "GET":
        return render(request, "login.html")
    username = request.POST.get("username")
    password = request.POST.get("pwd")

    user_obj = models.UserInfo.objects.filter(username=username, password=password).first()
    print(user_obj.username)

    if not user_obj:
        return redirect("/session_login/")
    else:
        request.session['is_login'] = True  # 设置session
        request.session['user1'] = username
        return redirect("/s_index/")

def s_index(request):
    status = request.session.get('is_login')  # 获取session
    if not status:
        return redirect('/session_login/')
    return render(request, "s_index.html")

def s_logout(request):
    # del request.session["is_login"] # 删除session_data里的一组键值对
    request.session.flush() # 删除一条记录包括(session_key session_data expire_date)三个字段
    return redirect('/session_login/')

Django 中间件

Django 中间件是修改 Django request 或者 response 对象的钩子,它可以在用户请求之后、服务响应之前做出一些特定的处理。

每个中间件都需要在 settings.py 中的 MIDDLEWARE 下进行配置。

自定义中间件

创建中间件文件:

# mydjango/app/middlewares.py
from django.utils.deprecation import MiddlewareMixin

class MD1(MiddlewareMixin):
    # 中间件需要继承MiddlewareMixin
    pass

settings.py 中进行注册:

# mydjango/mydjango/settings.py
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',
    'app.middlewares.MD1'
]

中间件方法

中间件类中可以定义如下方法:

  1. process_request
  2. process_view
  3. process_exception
  4. process_response

方法定义实例:

# mydjango/app/middlewares.py
from django.utils.deprecation import MiddlewareMixin
from django.shortcuts import render, HttpResponse


class MD1(MiddlewareMixin):
    def process_request(self, request):
        print("md1  process_request 方法。", id(request))  # 在视图之前执行

    def process_response(self, request, response):  # 基于请求响应
        print("md1  process_response 方法!", id(request))  # 在视图之后
        return response

    def process_view(self, request, view_func, view_args, view_kwargs):
        print("md1  process_view 方法!")  # 在视图之前执行 顺序执行
        return view_func(request)

    def process_exception(self, request, exception):  # 引发错误 才会触发这个方法
        print("md1  process_exception 方法!")
        return HttpResponse(exception)  # 返回错误信息

注:个人(基于 JAVA)理解,这里的【中间件】概念,其实相当于 Spring 的切面,或者拦截器等,能够在一个请求的整个生命周期中进行监控处理。

FBV & CBV

  1. FBV

    FBV(function base views)基于函数的视图,就是在视图里使用函数处理请求。

    其实就是上文中频繁使用的请求处理方式。

  2. CBV

    CBV(class base views)基于类的视图,就是在视图里使用类处理请求。

    CBV 主要通过 View 提供的静态方法 as_view() 来实现,as_view 方法是基于类的外部接口,他返回一个视图函数,调用后请求会传递给 dispatch 方法,dispatch 方法再根据不同请求来处理不同的方法。

    示例:

    from django.shortcuts import render,HttpResponse
    from django.views import View
    
    class Login(View):  # CBV类需要继承View
        def get(self,request):
            return HttpResponse("GET 方法")
    
        def post(self,request):
            user = request.POST.get("user")
            pwd = request.POST.get("pwd")
            if user == "runoob" and pwd == "123456":
                return HttpResponse("POST 方法")
            else:
                return HttpResponse("POST 方法 1")

    路由:

    urlpatterns = [
        path("login/", views.Login.as_view()),
    ]

Django 部署

Nginx + uwsgi

[❗️TODO]{.label .danger} 待完善

文末总结

写这一篇博客时,先是跟着 B 站视频学了一段,后面觉得讲得一般,就放弃了,转而跟着菜鸟教程编写笔记,断断续续,差不多用了两周多的时间。中途也在看 Django 开发的开源项目,感觉挺有趣的,这里强烈推荐一下:Django-Vue-Admin,也可能是因为我对若依 Java 那一套很熟悉,所以这个开源项目对我来说,要看懂也不太难。

参考

版权声明

本文链接:https://www.chinmoku.cc/python/django-tutorial/

本博客中的所有内容,包括但不限于文字、图片、音频、视频、图表和其他可视化材料,均受版权法保护。未经本博客所有者书面授权许可,禁止在任何媒体、网站、社交平台或其他渠道上复制、传播、修改、发布、展示或以任何其他方式使用此博客中的任何内容。

Press ESC to close