1. Django 介绍
MVC 模型
- 大部分开发语言中都有 MVC 开发模型。
- MVC 框架的核心思想是:解耦——降低各功能模块之间的耦合性,方便变更,更容易重构代码,最大程度上实现代码的重用。
- M 表示 Model,主要用于对数据库层的封装。
- V 表示 View,用于向用户展示结果。
- C 表示 Controller,是核心,用于处理请求、获取数据、返回结果。
MVT 模型
- Django 是一款 python 的 web 开发框架。
- 与 MVC 有所不同,属于 MVT 框架。
- M 表示 Model,负责与数据库交互。
- V 表示 View,是核心,负责接收请求、获取数据、返回结果。
- T 表示 Template,负责呈现内容到浏览器。
Django 开发流程范例
- 安装配置 Django 的运行环境, 参考上一篇文章:首次创建Django项目初始化-CSDN博客
- 创建项目、创建应用。
- 在应用的 model.py 定义模型类,执行迁移,生成对应的数据表(可使用简单 API 与数据库交互测试)。
- 使用 Django 的后台管理界面维护数据。
- 在应用的 views.py 定义视图,即处理核心逻辑的函数。
- 在项目目录下创建对应应用的模板目录,编写 html 页面(这种方式为前后端一体,也可以使用更加好维护的方式,前后端分离,vue作为前端部分)。
- 在项目和应用的 urls.py 中配置 URL 与视图的映射。
- 通过视图接收指定 URL 的访问请求,通过模型操作数据,通过模板填充数据并完成页面展示。
2. Django 环境搭建
参考上一篇文章:首次创建Django项目初始化-CSDN博客
2)创建工程项目
参考上一篇文章:首次创建Django项目初始化-CSDN博客
我们在上一篇文章中说过,创建完Django项目后,会得到一个完整的Django项目目录,接着上一篇,我们创建的项目名称为django-rebort
项目名称 django-rebort 中的目录说明
- manage.py:一个命令行工具,可以使你用多种方式对 Django 项目进行交互。
- django-rebort:与我们命名的项目名称一致,真正的本项目 python 包。
- _init _.py:一个空文件,它告诉 python 这个目录应该被看做一个 python 包。
- settings.py:项目的配置。
- urls.py:项目的 url 声明。
- wsgi.py:项目与 wsgi 兼容的 web 服务器入口。
3)创建应用
在一个项目中可以创建一到多个应用,一个应用专门处理一种业务。
参考上一篇文章 :首次创建Django项目初始化-CSDN博客
应用的目录结构如下图:
- migrations 包:根据模型类生成 SQL 语句,并作用到对应的数据库。
- admin.py:对本应用进行相关的管理。
- models.py:定义模型类(有多少个数据表,就有多少个模型类与之对应)。
- tests.py:Django 自带的测试模块,这里我没用,所以删除了
- views.py:定义视图相关函数。
将创建的应用配置进项目的 settings.py 中:(当不需要迁移时则可不做此步配置)
INSTALLED_APPS = (
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rebort-app' # 新增的应用
)
3. 模型(Model)
设计介绍
本示例完成“图书-英雄”两种信息的维护。“图书”、“英雄”的关系为一对多。
“图书”表结构设计:
- 表名:BookInfo
- 图书名称:btitle
- 图书发布时间:bpub_date
“英雄”表结构设计:
- 表名:HeroInfo
- 英雄姓名:hname
- 英雄性别:hgender
- 英雄简介:hcontent
- 所属图书:hbook
数据库配置
- 在 settings.py 文件中,通过 DATABASES 项进行数据库设置。
- Django 支持的数据库包括 SQLite、MySQL 等主流数据库。
- Django 默认使用自带的 SQLite 数据库。
定义模型类
步骤如下:
- 打开应用的 models.py 文件
- 引入包 from django.db import models
- 模型类继承 models.Model 类
- 说明:不需要定义主键列,在生成时会自动添加,并且值为自动增长
- 当输出对象时,会调用对象的 str 方法
1 from django.db import models
2
3 # 声明表名
4 class BookInfo(models.Model):
5 # 声明表字段
6 book_title = models.CharField(max_length=20) # 字符串类型且限定数据长度
7 book_public_date = models.DateTimeField() # 时间类型
8
9 def __str__(self):
10 return "%d" % self.pk # 返回主键列的值
11
12
13 class HeroInfo(models.Model):
14
15 name = models.CharField(max_length=20)
16 gender = models.BooleanField() # 布尔类型
17 content = models.CharField(max_length=100)
18 Book = models.ForeignKey('BookInfo') # 主/外键关联(BookInfo 加不加引号都行)
19
20 def __str__(self):
21 return "%d" % self.pk
迁移:生成数据表
1)激活模型:编辑 settings.py 文件,将 创建的app应用加入到 INSTALLED_APPS中
2)生成迁移文件:根据模型类生成表结构的相关 sql 脚本(记录关于 models.py 的所有改动,但是还没有作用到数据库中)
python manage.py makemigrations
迁移文件被生成到应用的 migrations 目录,迁移文件中就是模型定义的对应 sql 脚本。
3)执行迁移:即执行 sql 脚本,生成数据表(将 models.py 的所有改动作用到数据库中)
python manage.py migrate
4)Django模型修改以及迁移操作
Migrations
Django中对Model进行修改是件比较麻烦的事情,syncdb命令仅仅创建数据库里还没有的表,它并不对已存在的数据表进行同步修改,也不处理数据模型的删除。 如果你新增或修改数据模型里的字段,或是删除了一个数据模型,你需要手动在数据库里进行相应的修改或者使用South。Django 1.7中已经集成了South的代码,提供了3个新命令:
- migrate: 用于执行迁移动作,具有syncdb的功能
- makemigrations: 基于当前的model创建新的迁移策略文件
- sqlmigrate: 显示迁移的SQL语句,具有sqlall的功能
使用起来很简单,对Model做了修改后,使用makemigrations记录修改:
$ python manage.py makemigrations
Migrations for 'books':
0003_auto.py:
- Alter field author on book
你的Model会被扫描, 然后与migrations文件夹中以前的版本作比较, 然后生成本次迁移文件。
有了新的migration文件,就可以使用migrate修改数据库模式:
$ python manage.py migrate
Operations to perform:
Synchronize unmigrated apps: sessions, admin, messages, auth, staticfiles, contenttypes
Apply all migrations: books
Synchronizing apps without migrations:
Creating tables...
Installing custom SQL...
Installing indexes...
Installed 0 object(s) from 0 fixture(s)
Running migrations:
Applying books.0003_auto... OK
也可以针对单独的app生成migration:
$ python manage.py makemigrations rebort-app
也可以对数据库中的数据进行修改,首先建立一个空的migration文件:
python manage.py makemigrations --empty rebort-app
文件的内容如下:
# -*- coding: utf-8 -*-
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('rebort-app', '0001_initial'),
]
operations = [
]
如果想修改某个Model例如Person的数据,设置其name字段:
# -*- coding: utf-8 -*-
from django.db import models, migrations
def combine_names(apps, schema_editor):
# We can't import the Person model directly as it may be a newer
# version than this migration expects. We use the historical version.
Person = apps.get_model("yourappname", "Person")
for person in Person.objects.all():
person.name = "%s %s" % (person.first_name, person.last_name)
person.save()
class Migration(migrations.Migration):
dependencies = [
('rebort-app', '0001_initial'),
]
operations = [
migrations.RunPython(combine_names),
]
最后运行 python manage.py migrate即可。这样Person中的所有对象的name字段都设置好了。
依据Model修改关系数据库是开发中的一个重要的问题,解决这个问题可以提升开发速度,不过要在生产环境中随便使用migrate操作数据库还是很危险的,有时候需要手动修改数据库。
手动修改数据库
当处理模型修改的时候:
- 如果模型包含一个未曾在数据库里建立的字段,Django会报出错信息。 当你第一次用Django的数据库API请求表中不存在的字段时会导致错误。
- Django不关心数据库表中是否存在未在模型中定义的列。
- Django不关心数据库中是否存在未被模型表示的table。
添加字段
-
在你的模型里添加字段。下例向Book模型添加num_pages字段:
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author)
publisher = models.ForeignKey(Publisher)
publication_date = models.DateField()
**num_pages = models.IntegerField(blank=True, null=True)**
def __unicode__(self):
return self.title
-
运行manage.py sqlall yourappname来测试模型新的CREATE TABLE语句。
CREATE TABLE "books_book" (
"id" serial NOT NULL PRIMARY KEY,
"title" varchar(100) NOT NULL,
"publisher_id" integer NOT NULL REFERENCES "books_publisher" ("id"),
"publication_date" date NOT NULL,
"num_pages" integer NULL
);
-
开启你的数据库的交互命令界面(比如,psql或者mysql,或者可以使用manage.py dbshell。 执行ALTER TABLE语句来添加新列。
ALTER TABLE books_book ADD COLUMN num_pages integer;
添加 非NULL 字段
先创建 NULL 型的字段,然后将该字段的值填充为某个默认值,然后再将该字段改为 NOT NULL 型
BEGIN;
ALTER TABLE books_book ADD COLUMN num_pages integer;
UPDATE books_book SET num_pages=0;
UPDATE books_book SET num_pages = NULL;
COMMIT;
或者
ALTER TABLE <YourTable> ADD <NewColumn> <NewColumnType> NOT NULL DEFAULT <DefaultValue>;
添加ForeignKey或ManyToManyField
添加外键即是添加key_id的integer字段,添加多对多字段是创建一个新的数据表。
删除字段
比较简单,将表中的某列删掉即可
ALTER TABLE books_book DROP COLUMN num_pages;
使用sqlite3时,会有些麻烦,sqlite3不支持删除列操作,只有有限地 ALTER TABLE 支持。你可以使用它来在表的末尾增加一列,可更改表的名称。 如果需要对表结构做更复杂的改变,则必须重新建表。重建时可以先将已存在的数据放到一个临时表中,删除原表, 创建新表,然后将数据从临时表中复制回来。
如,假设有一个 t1 表,其中有 "a", "b", "c" 三列, 如果要删除列 c :
BEGIN TRANSACTION;
CREATE TEMPORARY TABLE t1_backup(a,b);
INSERT INTO t1_backup SELECT a,b FROM t1;
DROP TABLE t1;
CREATE TABLE t1(a,b);
INSERT INTO t1 SELECT a,b FROM t1_backup;
DROP TABLE t1_backup;
COMMIT;
删除多对多关联字段
删掉多对多关联的数据表即可
DROP TABLE books_book_authors;
删除模型
删除数据表即可
DROP TABLE books_book;
数据迁移
django 项目提供了一个导出的方法 python manage.py dumpdata, 不指定 appname 时默认为导出所有的app
python manage.py dumpdata rebort-app > rebort-app.json
导出的文件内容格式:
[
{
"model": "rebort-app.person",
"pk": 1,
"fields": {
"first_name": "John",
"last_name": "Lennon"
}
},
{
"model": "rebort-app.person",
"pk": 2,
"fields": {
"first_name": "Paul",
"last_name": "McCartney"
}
}
]
数据导入:
python manage.py loaddata rebort-app.json
导出用户数据:
python manage.py dumpdata auth > auth.json
数据操作测试
进入 python shell,进行简单的模型 API 练习:
python manage.py shell
操作“图书”对象
# 引入需要的包
>>> from booktest.models import BookInfo,HeroInfo
>>> from django.utils import timezone
>>> from datetime import *
# 查询所有图书数据
>>> BookInfo.objects.all()
[]
# 新建图书数据
>>> b = BookInfo() # 映射表名
>>> b.book_title = "射雕英雄传" # 映射表字段
>>> b.book_public_date = datetime(year=1990, month=1, day=10)
>>> b.save()
# 查找图书数据
>>> b = BookInfo.objects.get(pk=1) # 根据主键查找
# 输出图书数据
>>> b
<BookInfo: 1>
>>> b.id
1
>>> b.book_title
'射雕英雄传'
# 修改图书数据
>>> b.book_title = "天龙八部"
>>> b.save()
# 删除图书数据
>>> b.delete()
操作“英雄”对象:关联对象的操作
- 对于 HeroInfo 可以按照上面的操作方式进行。
- 注意添加关联对象(图书)。
>>> h = HeroInfo()
>>> h.name = "郭靖"
>>> h.gender = True
>>> h.content = "降龙十八掌"
>>> h.Book = b
>>> h.save()
# 获得关联集合:返回当前BookInfo对象的所有HeroInfo对象
>>> b.heroinfo_set.all()
[<HeroInfo: 1>]
# 有一个HeroInfo对象存在,就必须对应一个BookInfo对象
# 另一种创建关联的方式
>>> h = b.heroinfo_set.create(name="黄蓉", gender=False, content="打狗棒法")
>>> h
<HeroInfo: 2>
>>> h.name
'黄蓉'
4. 站点管理
1)服务器启停
运行如下命令可以开启服务器:
python manage.py runserver ip:port
修改端口:
python manage.py runserver 8080
- 可以不写 ip,默认端口为 8000。
- 这是一个纯 python 编写的轻量级 web 服务器,仅在开发阶段使用。
- 服务器成功启动后,提示如下信息:
E:\DjangoDemo>python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
March 31, 2025 - 9:23:43
Django version 1.8.2, using settings 'DjangoDemo.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
- 打开浏览器,输入网址“127.0.0.1:8000”可以打开默认页面。
- 修改文件后不需要重启服务器;如果是增删文件则需要重启服务器。
- 通过 ctrl+c 停止服务器。
2)管理操作
- 站点分为“内容发布”和“公共访问”两部分。
- “内容发布”的部分负责添加、修改、删除内容,开发这些重复的功能是一件单调乏味、缺乏创造力的工作。为此,Django 会根据定义的模型类完全自动地生成管理模块。
使用 django 的管理
- 执行以下命令创建一个管理员用户,按提示输入用户名、密码、邮件:
python manage.py createsuperuser
- 启动服务器,通过“127.0.0.1:8000/admin”访问,输入上面创建的用户名、密码完成登录;
- 进入管理站点,默认可以对 groups、users 进行管理。
管理界面本地化
- 编辑 settings.py 文件,设置中文编码和中国时区,上一篇中有提到,可以参考上一篇文章
LANGUAGE_CODE = 'zh-Hans'
TIME_ZONE = 'Asia/Shanghai'
向 admin 注册 hero_info 的相关模型
- 打开 hero_book/admin.py 文件,注册模型
from django.contrib import admin
from models import BookInfo
admin.site.register(BookInfo)
admin.site.register(HeroInfo)
- 刷新管理页面,可以对 BookInfo 的数据进行增删改查操作。
- 问题:如果要在 str() 方法中返回中文,在修改和添加时会报 ascii 的错误。
- 解决:在 str() 方法中,将字符串末尾添加“.encode('utf-8')”。
自定义管理页面
- Django 提供了 admin.ModelAdmin 类。
- 通过自定义 ModelAdmin 的子类,来定义模型在站点管理界面的显示方式。
列表页的相关属性
1 from django.contrib import admin
2 from .models import *
3
4
5 # 自定义ModelAdmin的子类,定义BookInfo模型在站点管理界面的显示方式
6 class BookInfoAdmin(admin.ModelAdmin):
7 list_display = ['pk', 'book_title', 'book_public_date'] # list_display:显示字段,可以点击列头进行排序
8 list_filter = ['book_title'] # list_filter:过滤字段,过滤框会出现在右侧
9 search_fields = ['book_title'] # search_fields:搜索字段,搜索框会出现在上侧
10 list_per_page = 10 # list_per_page:分页,分页框会出现在下侧
11
12
13 admin.site.register(BookInfo, BookInfoAdmin) # 将ModelAdmin子类与对应的模型类放一起
14 admin.site.register(HeroInfo)
界面效果:
添加/修改页的相关属性
1 from django.contrib import admin
2 from .models import *
3
4
5 # 自定义ModelAdmin的子类,定义BookInfo模型在站点管理界面的显示方式
6 class BookInfoAdmin(admin.ModelAdmin):
7
8 '''列表页属性'''
9 list_display = ['pk', 'book_title', 'book_public_date'] # list_display:显示字段,可以点击列头进行排序
10 list_filter = ['book_title'] # list_filter:过滤字段,过滤框会出现在右侧
11 search_fields = ['book_title'] # search_fields:搜索字段,搜索框会出现在上侧
12 list_per_page = 10 # list_per_page:分页,分页框会出现在下侧
13
14 '''添加、修改页属性'''
15 # fields:属性的先后顺序
16 # fields = ['book_public_date', 'book_title']
17 # fieldsets:属性分组
18 fieldsets = [
19 ('basic', {'fields': ['book_title']}),
20 ('more', {'fields': ['book_public_date']}),
21 ]
22
23
24 admin.site.register(BookInfo, BookInfoAdmin) # 将ModelAdmin子类与对应的模型类放一起
25 admin.site.register(HeroInfo)
页面效果:
3)关联对象
对于 HeroInfo 模型类,有两种注册方式:
- 方式一:与 BookInfo 模型类相同的注册方式
- 方式二:关联注册
接下来实现关联注册
1 from django.contrib import admin
2 from .models import *
3
4
5 # 自定义admin.StackedInline的子类,实现关联对象的注册
6 class HeroInfoInline(admin.StackedInline):
7 model = HeroInfo
8 extra = 2
9
10
11 # 自定义ModelAdmin的子类,定义BookInfo模型在站点管理界面的显示方式
12 class BookInfoAdmin(admin.ModelAdmin):
13
14 '''列表页的相关属性'''
15 list_display = ['pk', 'book_title', 'book_public_date'] # list_display:显示字段,可以点击列头进行排序
16 list_filter = ['book_title'] # list_filter:过滤字段,过滤框会出现在右侧
17 search_fields = ['book_title'] # search_fields:搜索字段,搜索框会出现在上侧
18 list_per_page = 10 # list_per_page:分页,分页框会出现在下侧
19
20 '''添加/修改页的相关属性'''
21 # fields:属性的先后顺序
22 # fields = ['book_public_date', 'book_title']
23 # fieldsets:属性分组
24 fieldsets = [
25 ('basic', {'fields': ['book_title']}),
26 ('more', {'fields': ['book_public_date']}),
27 ]
28
29 '''实现关联对象的方式二'''
30 inlines = [HeroInfoInline]
31
32
33 admin.site.register(BookInfo, BookInfoAdmin) # 将ModelAdmin子类与对应的模型类放一起
34 # admin.site.register(HeroInfo) # 实现关联对象的方式一
还可以将内嵌的方式改为表格,只需替换继承的父类:
class HeroInfoInline(admin.TabularInline)
4)布尔值的显示
发布性别的显示不是一个直观的结果,可以使用方法进行封装:
1 def gender(self):
2 if self.hgender:
3 return '男'
4 else:
5 return '女'
6
7 gender.short_description = '性别'
在 admin 注册中使用函数名 gender 代替类属性 gender:
class HeroInfoAdmin(admin.ModelAdmin):
list_display = ['id', 'name', 'gender', 'content']
5. 视图(View)
views.py
- 在 Django 中,视图负责对 web 请求进行回应。
- 视图接收 reqeust 对象作为第一个参数,该参数包含了请求的信息。
- 视图就是一个 python 函数,被定义在 views.py 中。
1 from django.http import HttpResponse # 引入响应对象
2
3 # 定义访问主页时的响应
4 def index(request): # 视图函数的第一个参数必须是request(请求)对象
5 return HttpResponse("welcome index page!") # 视图函数返回的必须是HttpResponse(响应)对象或其子类
6
7 # 定义访问详情页时的响应
8 def detail(request, id):
9 return HttpResponse("detail:%s" % id)
定义完成视图后,需要配置 URLconf,否则无法处理请求。
URLconf
- 在 Django 中,定义 URLconf 包括两方面内容:配置正则表达式和视图。
- Django 使用正则表达式匹配请求 URL,一旦匹配成功,则调用对应的视图。
- 注意:只匹配路径部分,即除去域名、参数后的字符串。
- 在 DjangoDemo/urls.py 中新增 hero_book.urls,使主 URLconf 连接到 hero_book.urls 模块。
注意:django 2.0 起,urls 不支持正则表达式问题。如果需要使用正则,需要导入 re_path,使用方法如下:
from django.urls import path, re_path
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^test-(\d+)-(\d+)/', views.test),
path('index/', views.index),
]
示例
DjangoDemo/urls.py:
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^', include('hero_book.urls')), # 关联hero_book包下的urls模块
]
hero_book/urls.py:
1 from django.conf.urls import url
2 from . import views
3
4
5 urlpatterns = [
6 url(r'^$', views.index), # URI不带多余字符时访问index函数
7 url(r'^book/([0-9]+)/$', views.detail), # URI带数字时访问detail函数
8 ]
页面效果
6. 模板(Template)
模板其实就是 html 页面,可以根据视图中传递的数据填充值。
(实际上,文件扩展名并不一定要是 html。只要文件中是 html 的内容,使用其他文件扩展名也可。)
创建模板目录
1)在项目目录下创建模板的目录,如下图:
为了清晰起见,一个模板目录对应一个应用,存放该应用的所有页面。
2)修改应用的 settings.py 文件,设置 TEMPLATES 的 DIRS 值:
'DIRS': [os.path.join(BASE_DIR, "templates")], # 关联到项目目录下的templates目录(BASE_DIR是setings.py中已定义好的变量)
定义模板
在模板中访问视图传递的数据的方式:
{{输出值,可以是变量,也可以是对象.属性}}
{%执行代码段%}
index.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>首页</title>
6 </head>
7 <body>
8 <h1>图书列表</h1>
9 <ul>
10 {%for book in booklist%}
11 <li>
12 <a href="book/{{book.id}}">{{book.book_title}}</a>
13 </li>
14 {%endfor%}
15 </ul>
16 </body>
17 </html>
detail.html
1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>详情页</title>
6 </head>
7 <body>
8 <h1>编号:{{book.id}}</h1>
9 <ul>
10 {%for hero in book.heroinfo_set.all%}
11 <li>{{hero.name}}——{{hero.content}}</li>
12 {%endfor%}
13 </ul>
14 </body>
15 </html>
使用模板
编辑 views.py,在方法中调用对应的模板:
1 from django.shortcuts import render
2 from .models import BookInfo
3
4 # 首页
5 def index(request):
6 # 获取所有BookInfo对象
7 booklist = BookInfo.objects.all()
8 # render 参数2:指定模板文件;参数3:将booklist对象列表传递给模板页面中引用
9 return render(request, 'hero_book/index.html', {'booklist': booklist})
10
11 # 详情页
12 def detail(request, id):
13 # 将url正则中的分组作为主键,获取对应的BookInfo对象
14 book = BookInfo.objects.get(pk=id)
15 return render(request, 'hero_book/detail.html', {'book': book})
页面效果
Django相对于Flask框架,稍微会在操作上有点复杂,如果你对Flask感兴趣,可以看我Flask框架部分,将带你从0开发一个平台的完整介绍