这篇文章是在 Python 基础知识全通关的基础上进行的进阶学习。
注意:由于本博客主题使用 Prism.js
对代码段进行渲染,渲染过程中,会与代码块中的双 {
和 {
+ %
的语法产生冲突,因此,本文将所有相关符号都替换为 {.{
和 {.%
形式,以解决代码渲染的问题。读者在阅读和拷贝代码的过程中,请务必注意该问题并忽略符号之间多出的 .
,如因此造成阅读上的不便,还请见谅。
# 学习前提
# 知识储备要求
- 了解 Python 基础,如果不了解,请先行阅读《Python 基础知识全通关》。
- 拥有前端开发相关基础技能,至少包括:CSS、JS、HTML。
- 会使用常用的 Liunx 命令。
# 环境说明
本篇文章在编写过程中,将以如下信息作为参考环境:
- 系统信息:VMware 16.1.2 + CentOS 8.4, windows10
- Python 版本:Python 3.9.6
- 开发工具:Pycharm
- 命令行工具:Termius
关于 linux 环境,本文仅在安装、部署等少数部分提供相关说明,其他大部分环境将在 windows 下进行。
# 简介
Django 是一个由 Python 编写的一个开放源代码的 Web 应用框架,Django 本身基于 MVC 模型。
Django 框架使用 MTV 模式,但其本质上和 MVC 是一样的,也是为了各组件间保持松耦合关系,只是定义上有些许不同,Django 的 MTV 分别是指:
- Model:编写程序应有的功能,负责业务对象与数据库的映射(ORM)。
- Template:负责如何把页面(html)展示给用户。
- View:负责业务逻辑,并在适当时候调用 Model 和 Template。
除了以上三层之外,还需要一个 URL 分发器,它的作用是将一个个 URL 的页面请求分发给不同的 View 处理,View 再调用相应的 Model 和 Template。
# 安装与初始化
# 虚拟环境安装
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 # 退出虚拟环境 |
提示:
- 如果在虚拟机创建过程中提示
-bash: virtualenv: command not found
,这是因为/usr/bin
中还未创建软连接。- 查找 virtualenv 所在位置:
find / -name virtualenv
(稍微耗时一点)。- 创建 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 # 启动项目 |
测试访问:
注:如需通过外部网络访问虚拟机内部提供的服务,需要配置相应的防火墙出入站规则。
另外,如需共享文件夹,可参考:
- Win10 下与虚拟机中的 linux 共享文件夹
- VMware 与 win10 共享文件夹
# 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 中启动项目就更简单了,直接点击右上角的运行按钮即可。
# 项目结构
在 Pycharm 中配置好环境,后续操作在编辑器中进行,将会更加方便。
生成页面子应用:
python manage.py startapp index |
执行完成后,目录结构如下图所示:
# 项目配置
与项目同名的子文件夹中存放了项目相关配置,部分文件说明如下:
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 模板
# 准备工作
使用虚拟环境创建项目
mydjango
。创建应用
app
。- app
- migrations
__init__.py
__init__.py
admin.py #
apps.py #
models.py # 数据库连接文件
tests.py # 测试文件
views.py # 视图文件
urls.py # 子路由信息,通过手动创建
注意:后文中会出现一些
mydjango
和app
,并不是什么特殊名称,而是指当前项目和应用文件夹的名称,项目创建不同,自然就不同。尝试启动项目,确保没有问题。
在 mydjango 项目下创建文件夹
templates
和static
分别用于存放模板文件和静态资源。修改 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) |
当返回数据时,直接访问路由地址,即可获取到对应的数据。当返回视图模板时,会将参数传递到模板中,模板会解析参数,并补充到对应位置,此处使用到的模板如下:
<!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) |
在模板中对列表进行遍历:
<!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 也提供了一些方法帮助我们获取循环相关的信息:
- forloop.counter
- forloop.counter0:下标从 0 开始。
- forloop.revcounter
- forloop.revcounter0
- forloop.first
- forloop.last
- 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) |
使用:
<p>生日:{.{ info.birthday }}</p> | |
<p>年龄:{.{ info.age }}</p> |
# 模板:过滤器
基本语法: {.{ var | filter_name: not_required_params }}
。
过滤器的使用方式大概是这样:
<p>年龄:{.{ info.age | add:18 }}</p> |
按照这样的使用方式,就可以将原有的值,通过某种规则,返回一个被计算或处理后的值,例如这里的自增 18。
这种类似的被 Django 内置支持的过滤器还有不少,可以在网上看一看。
根据万物皆可套娃原则,过滤器也可以这样使用:
<p>年龄:{.{ info.age | add:18 | divisibleby:5 | upper | linenumbers | length }}</p> |
以上是 Django 内置的过滤器,但在业务过程中,有时也需要对过滤器进行自定义,这里提供一下自定义过滤器的流程:
在 app 应用下创建文件夹
templatetags
(固定名称)。在 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
在模板中进行使用。
{.% 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' %}" /> |
# 自定义标签
自定义标签步骤:
应用目录下创建 templatetags 目录。
在 templatetags 目录下创建任意文件,如
my_tags.py
。配置文件中指定配置。
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'
}
}
},
]
编辑 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
在模板文件中使用
{.% 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> |
# 其他模板知识
注释
include 标签
include 标签允许在模板之中包含其他模板:
{.% include "nav.html" %}
csrf_token
csrf_token
用于 form 表单中,作用是跨站请求伪造保护,使用这个标签,表单提交数据才会成功,否则进行再次跳转页面时会抛出 403 错误。其他模板如
jinja2
、mako
等,了解即可,如有需求,自行摸索。
# 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
(表名与当前模块和模型内的类名有关)。
提示:
如需在 PyCharm Terminal 中自动激活虚拟环境,可在 settings -> Tools -> Terminal -> Shell path 中进行类似如下的配置:
"cmd.exe" /k ""A:\python-venv\django_venv\Scripts\activate""
执行
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
创建模板文件:
<!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 中的属性主要有:
- path
- method
- GET
- POST
- REQUEST:为了方便,该属性是 POST 和 GET 属性的集合体,但是有特殊性,先查找 POST 属性,然后再查找 GET 属性。(不建议使用)
- COOKIES
- FILES
- META:包含所有可用 HTTP 头部信息的字典。
- user:是一个
django.contrib.auth.models.User
对象,代表当前登录的用户。可以通过 user 的is_authenticated()
方法来辨别用户是否登录。 - session
- raw_post_data:原始 HTTP POST 数据,未解析过。高级处理时会有用处。
request 也提供了一些方法:
__getitem__(key)
:返回 GET/POST 的键值,先取 POST,后取 GET。如果键不存在则抛出 KeyError。has_key()
:检查 request.GET or request.POST 中是否包含参数指定的 Key。get_full_path()
is_secure()
:检验 HTTPS。
# QueryDict 对象
在 HttpRequest 对象中,GET 和 POST 属性是 django.http.QueryDict 类的实例。QueryDict 类似字典的自定义类,用来处理单键对应多值的情况,它不仅实现所有标准的词典方法,还包括了一些特有的方法:
__getitem__(key)
:和标准字典的处理有一点不同,就是,如果 Key 对应多个 Value,则会返回最后一个 value。__setitem__(key, value)
:这里的 value 是一个 list,它只能在一个 mutable QueryDict 对象上被调用(即通过 copy 产生的一个 QueryDict 对象的拷贝)。get()
:如果对应多个 value,则返回最后一个。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
items()
:和标准字典同名方法有一点不同,该方法使用单值逻辑的 _getitem_()。q = QueryDict('a=1&a=2&a=3')
print(q.items()) # [('a', '3')]
values()
:同样是单值。copy
getlist
setlist
appendlist
setlistdefault
lists
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 中的每一条配置对应相应的处理方法。
# 路由配置方式
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), # 正则路径
]
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:用于正则路径,需要自己手动添加正则首位限制符号。
# 正则路由
正则路径中的无名分组
# 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!')
正则路径中的有名分组
# 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)
有名分组会在路由中指定参数名称,视图解析时则不需要按照规定顺序,只需要根据名称进行匹配即可。
路由分发
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 进行后续处理。
在反向解析路由时,同样可以传递参数,包括有名和无名分组在内,传递参数方式如下:
<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')) |
在模板中的使用方式也是类似:
<!--swig12--> | |
<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
来创建超级用户,创建成功后,即可登录,登录成功后将跳转如下页面:
如需在 admin 界面管理某个数据模型,只需要在应用下的 admin.py 文件中注册对应的数据模型即可:
from django.contrib import admin | |
from app.models import Test | |
# Register your models here. | |
admin.site.register(Test) |
然后刷新 admin 界面,即可看到对应的数据模型。
# 自定义表单
自行摸索吧,感觉没啥意思。
# Django ORM
# 单表实例
准备工作
注册当前应用:
# 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()
创建模型
# 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
表。新增数据
编写新增数据的视图函数:
# 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)
查询数据
查询列表
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 也内置了一些其他常用的筛选方式,通过如下提示即可查看:
关于过滤规则及其意义,可以点击此处作为参考,这里不反复书写示例。
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()
修改数据
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>')
删除数据
books=models.Book.objects.filter(pk=5).delete()
# books = models.Book.objects.all ().delete () # 删除所有
注意:数据删除属于非常规操作,在业务过程中,一般删除都进行逻辑删除,而非物理删除。
# 多表实例
表与表之间的关系,通常有如下三种:
- 一对一。
- 一对多。
- 多对多。
注意:这种对应关系时相对而言的,若甲与乙是一对多关系,那么,乙与甲则是一对一关系(也可以说是多对一),反之,则不一定成立。
准备工作
移除上一实例生成的数据库表,以及删除对应的数据模型,或者重新新建应用。
清除 mydjango/app/migrations 下的 sql 脚本文件。
删除数据库表
django_migrations
中旧有生成记录。delete from django_migrations where app = 'app'
创建模型
# 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()
说明:
- EmailField 数据类型是邮箱格式,底层继承 CharField,进行了封装,相当于 MySQL 中的 varchar。
- Django1.1 版本不需要联级删除:on_delete=models.CASCADE,Django2.2 需要。
执行数据迁移命令:
python3 manage.py migrate
python3 manage.py makemigrations app
python3 manage.py migrate app
执行完毕后,数据库会生成如下表结构:
初始化数据
为方便后续操作,为当前生成的表结构初始化部分数据:
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);
注意:由于表之间存在外键关联关系,因此,在手动插入数据时,需要遵循关联顺序进行插入。
新增数据
一对多添加数据:
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() # 移除所有关联
查询数据
# 查询某书籍出版社位置
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)
双下划线跨表查询
# 查询某出版社书籍及价格
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 标签,此外还可以对用户提交对数据进行校验(显示错误信息)。
# 使用方式
创建 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="工资")
视图处理逻辑
# 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)
新建模板文件
<!-- 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 |
# 用户级别
可以通过如下方法创建用户对象:
create()
创建一个普通用户,密码是明文的。User.objects.create(username='test', password='123456')
create_user()
创建一个普通用户,密码是密文的。User.objects.create_user(username='zhangsan', password='123456')
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' | |
] |
# 中间件方法
中间件类中可以定义如下方法:
- process_request
- process_view
- process_exception
- 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
FBV
FBV(function base views)基于函数的视图,就是在视图里使用函数处理请求。
其实就是上文中频繁使用的请求处理方式。
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 待完善
# 文末总结
写这一篇博客时,先是跟着 B 站视频学了一段,后面觉得讲得一般,就放弃了,转而跟着菜鸟教程编写笔记,断断续续,差不多用了两周多的时间。中途也在看 Django 开发的开源项目,感觉挺有趣的,这里强烈推荐一下:Django-Vue-Admin,也可能是因为我对若依 Java 那一套很熟悉,所以这个开源项目对我来说,要看懂也不太难。
# 参考
- https://www.bilibili.com/video/BV1i3411i7aL
- https://www.runoob.com/django/django-tutorial.html