Django 网站全栈开发教程
这篇文章是在 Python 基础知识全通关的基础上进行的进阶学习。
注意:由于本博客主题使用 Prism.js
对代码段进行渲染,渲染过程中,会与代码块中的双 {
和 {
+ %
的语法产生冲突,因此,本文将所有相关符号都替换 [[{{]] 为 {\{
和 {%
形式,以解决代码渲染的问题。读者在阅读和拷贝代码的过程中,请务必注意该问题并忽略符号之间多出的 .
,如因此造成阅读上的不便,还请见谅。
学习前提
知识储备要求
- 了解 Python 基础,如果不了解,请先行阅读《Python 基础知识全通关》。
- 拥有前端开发相关基础技能,至少包括:CSS、JS、HTML。
- 会使用常用的 Liunx 命令。
环境说明
本篇文章在编写过程中,将以如下信息作为参考环境:
- 系统信息:[VMware 16.1.2]{.label .primary} + [CentOS 8.4]{.label .info}, [windows10]{.label .info}
- Python 版本:[Python 3.9.6]{.label .success}
- 开发工具:[Pycharm]{.label .danger}
- 命令行工具:[Termius]{.label .warning}
关于 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 # 启动项目
测试访问:
注:如需通过外部网络访问虚拟机内部提供的服务,需要配置相应的防火墙出入站规则。
另外,如需共享文件夹,可参考:
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)
当返回数据时,直接访问路由地址,即可获取到对应的数据。当返回视图模板时,会将参数传递到模板中,模板会解析参数,并补充到对应位置,此处使用到的模板如下:
{# 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 也提供了一些方法帮助我们获取循环相关的信息:
- 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)
使用:
{# 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 内置的过滤器,但在业务过程中,有时也需要对过滤器进行自定义,这里提供一下自定义过滤器的流程:
-
在 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
-
在模板中进行使用。
{# 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' %}" />
自定义标签
自定义标签步骤:
-
应用目录下创建 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
-
在模板文件中使用
{# 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>
其他模板知识
-
注释
{# 这是一段注释信息 #}
-
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
创建模板文件:
{# 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 中的属性主要有:
- 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 进行后续处理。
在反向解析路由时,同样可以传递参数,包括有名和无名分组在内,传递参数方式如下:
{# 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
来创建超级用户,创建成功后,即可登录,登录成功后将跳转如下页面:
如需在 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]{.label .danger} 待完善
文末总结
写这一篇博客时,先是跟着 B 站视频学了一段,后面觉得讲得一般,就放弃了,转而跟着菜鸟教程编写笔记,断断续续,差不多用了两周多的时间。中途也在看 Django 开发的开源项目,感觉挺有趣的,这里强烈推荐一下:Django-Vue-Admin,也可能是因为我对若依 Java 那一套很熟悉,所以这个开源项目对我来说,要看懂也不太难。
参考
著作権声明
本記事のリンク:https://www.chinmoku.cc/dev/python/django-tutorial/
本博客中的所有内容,包括但不限于文字、图片、音频、视频、图表和其他可视化材料,均受版权法保护。未经本博客所有者书面授权许可,禁止在任何媒体、网站、社交平台或其他渠道上复制、传播、修改、发布、展示或以任何其他方式使用此博客中的任何内容。