Django 是由 Python 语言编写的基于 MVC(即 Model View Controller)架构的 Web 开发框架。
其架构中的模型(Model)主要负责处理 Web 应用的数据逻辑部分,包括定义数据存储单位(即数据库表)的字段属性和行为、与数据库交互以及其他相关联的操作。
通常一个模型映射于一个特定的数据库表。
Django 中的模型有以下几个基本属性:
- 每个模型都是继承自
django.db.models.Model类的子类 - 模型类的属性分别对应于与之相关联的数据表中的字段
- Django 会自动生成用于访问数据库的 API
一、基本使用
项目初始化
在开始编写 Web 应用代码之前,需要先使用如下命令初始化一个 Django 项目并创建应用:1
2
3$ django-admin startproject myproject
$ cd myproject
$ python manage.py startapp myapp
最终生成的项目目录结构如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15myproject
├─manage.py
│
├─myapp
│ ├─admin.py
│ ├─apps.py
│ ├─models.py
│ ├─tests.py
│ ├─views.py
│ └─migrations
│
└─myproject
├─settings.py
├─urls.py
└─wsgi.py
定义模型
用于定义模型的代码通常保存在 myproject/myapp/models.py 文件中。
下面的代码即定义了一个简单的 Person 模型:1
2
3
4
5from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
其中的 first_name 和 last_name 两个类属性即对应于数据库表的两个字段。Person 模型会以如下的 SQL 语句创建与之关联的数据库表(id 字段默认会自动添加):1
2
3
4
5CREATE TABLE myapp_person (
"id" serial NOT NULL PRIMARY KEY,
"first_name" varchar(30) NOT NULL,
"last_name" varchar(30) NOT NULL
);
为了使模型生效,还需要将 myapp 包含进 settings.py 配置文件中的 INSTALLED_APPS,编辑 myproject/myproject/settings.py 文件,内容如下:1
2
3
4
5INSTALLED_APPS = [
#...
'myapp',
#...
]
之后即可使用 python manage.py makemigrations myapp 创建数据库迁移文件;
再运行 python manage.py migrate 命令将模型中定义的表结构迁移至数据库中。
Django Shell 测试
完成数据库迁移后,可使用 python manage.py shell 命令进入 Django Shell 交互式命令行,通过 Django 提供的模型 API 进行测试(插入数据):1
2
3
4
5
6
7>>> from myapp.models import Person
>>> john = Person(first_name='John', last_name='Smith')
>>> john.save()
>>> Person.objects.all()
<QuerySet [<Person: Person object (1)>]>
>>> john.first_name
'John'
访问 sqlite3 数据库查询最终结果,John Smith 已添加至数据表中:1
2
3
4
5
6
7>>> import sqlite3
>>> conn = sqlite3.connect('db.sqlite3')
>>> cursor = conn.cursor()
>>> cursor.execute('select * from myapp_person')
<sqlite3.Cursor object at 0x0000022C36ADBD50>
>>> print(cursor.fetchone())
(1, 'John', 'Smith')
二、字段(Field)
模型中最重要的也是唯一必须存在的项目就是字段,它由模型类的属性定义,用来表述与模型相关联的数据表的结构。
字段的类型与选项
模型中的每个字段都是 django.db.models.Field 类的实例,对应于数据库表中的列。
Django 内置了大量的字段类型,如 CharField,TextField 和 DateTimeField 等。具体可查看 模型字段参考。
每个字段都可以接收特定的字段相关的参数,比如 CharField 需要传入 max_length 用于定义 VARCHAR 类型的字符长度。
此外还有一些通用的可选的字段选项。如:
null:如为 True,则 Django 会将空值在数据库中存为 NULL。该选项默认为 False。blank:如为 True,则该字段允许为空。与null选项不同,blank是与表单验证相关的,而null是数据库相关的。default:用于设置字段的默认值。primary_key:用于设置模型的主键。如未指定任何字段为主键,则 Django 会自动添加IntegerField字段作为主键。unique:设置字段的值是否允许重复。
PS:默认情况下,Django 会给每个模型添加如下字段id = models.AutoField(primary_key=True)
作为为自增的主键。如果想覆盖此默认行为,直接手动指定其他字段为主键(primary_key=True)即可。
关系
额,关系型数据库的强大之处即在于各数据库表之间的相互关联。Django 支持定义三种最常见的数据库关系:多对一、多对多和一对一。
可以通过 django.db.models.ForeignKey 创建多对一关系,只需要像定义其他字段那样将它作为类属性引入即可。如:1
2
3
4
5
6
7
8
9from django.db import models
class Manufacturer(models.Model):
name = models.CharField(max_length=20)
location = models.CharField(max_length=40)
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
price = models.IntegerField()
运行 python manage.py makemigrations 命令创建数据库迁移文件:1
2
3
4
5$ python manage.py makemigrations myapp
Migrations for 'myapp':
myapp/migrations/0001_initial.py
- Create model Manufacturer
- Create model Car
使用 python manage.py sqlmigrate myapp 0001 命令查看具体会执行哪些 SQL 语句(基于 sqlite3):1
2
3
4
5
6
7
8
9
10
11
12$ python manage.py sqlmigrate myapp 0001
BEGIN;
--
-- Create model Manufacturer
--
CREATE TABLE "myapp_manufacturer" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "name" varchar(20) NOT NULL, "location" varchar(40) NOT NULL);
--
-- Create model Car
--
CREATE TABLE "myapp_car" ("id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, "price" integer NOT NULL, "manufacturer_id" integer NOT NULL REFERENCES "myapp_manufacturer" ("id") DEFERRABLE INITIALLY DEFERRED);
CREATE INDEX "myapp_car_manufacturer_id_2be676ab" ON "myapp_car" ("manufacturer_id");
COMMIT;
多对多和一对一的数据库关系则分别可以使用 ManyToManyField 和 OneToOneField 定义。
三、模型的属性与方法
Meta 选项
模型的 Meta 选项在模型类的定义中是可选的,它基本上包含了除字段以外的所有内容。比如数据纪录的顺序(ordering)、关联的数据库表的名称(db_table)和索引(indexes)等。
Django 模型支持的所有 Meta 选项可以参考 Model Meta options
示例代码:1
2
3
4
5
6
7
8from django.db import models
class Ox(models.Model):
horn_length = models.IntegerField()
class Meta:
ordering = ["horn_length"]
verbose_name_plural = "oxen"
自定义模型方法
在模型中创建自定义方法可以为模型对象添加个性化的“底层”功能。参考如下代码:1
2
3
4
5
6
7
8
9
10
11from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
birth_date = models.DateField()
@property
def full_name(self):
"Returns the person's full name."
return '%s %s' % (self.first_name, self.last_name)
此时的 Person 模型除了可以从数据库中读取和写入数据等基本功能外,还可以通过它调用自定义的 full_name 方法完成额外的需求(返回全名)。
覆盖默认的模型方法
有些情况下,还可以通过修改模型内置的方法,改变模型与数据库的具体交互方式。尤其是 save() (向数据库中存入数据)和 delete() (从数据库中删除纪录)等方法。如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15from django.db import models
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
def save(self, *args, **kwargs):
self.first_name = self.first_name.capitalize()
self.last_name = self.last_name.capitalize()
super().save(*args, **kwargs)
@property
def full_name(self):
"Returns the person's full name."
return '%s %s' % (self.first_name, self.last_name)
重新迁移数据库,进入 Django Shell 测试,结果如下:1
2
3
4
5
6
7>>> from myapp.models import Person
>>> john = Person(first_name='john', last_name='smith')
>>> john.save()
>>> john
<Person: Person object (2)>
>>> john.full_name
'John Smith'
四、数据库操作
一旦创建了数据模型,Django 即会自动生成与数据库交互的 API 供用户创建、获取、更新和删除数据对象。
此处先创建如下的模型文件供测试使用:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29from django.db import models
class Blog(models.Model):
name = models.CharField(max_length=100)
tagline = models.TextField()
def __str__(self):
return self.name
class Author(models.Model):
name = models.CharField(max_length=200)
email = models.EmailField()
def __str__(self):
return self.name
class Entry(models.Model):
blog = models.ForeignKey(Blog, on_delete=models.CASCADE)
headline = models.CharField(max_length=255)
body_text = models.TextField()
pub_date = models.DateField()
mod_date = models.DateField()
authors = models.ManyToManyField(Author)
n_comments = models.IntegerField()
n_pingbacks = models.IntegerField()
rating = models.IntegerField()
def __str__(self):
return self.headline
Insert
可以通过实例化模型类创建一个数据对象,并调用其 save() 方法将对应的记录插入(执行 INSERT SQL 语句)到数据库表中。1
2
3
4
5
6
7
8
9>>> from myapp.models import Blog
>>> b = Blog(name='Beatles Blog', tagline='All the latest Beatles news')
>>> b.save()
>>> b.name = 'Beatles Blog All'
>>> b.save()
>>> b
<Blog: Beatles Blog All>
>>> b.tagline
'All the latest Beatles news'
插入 ForeignKey 与 ManyToManyField
更新 ForeignKey 与操作普通字段的方式相同,将正确类型的对象赋值给对应字段并调用 save() 方法即可。如:1
2
3
4
5>>> from blog.models import Blog, Entry
>>> entry = Entry.objects.get(pk=1)
>>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
>>> entry.blog = cheese_blog
>>> entry.save()
更新 ManyToManyField 的方式稍有不同,需要使用 add() 方法:1
2
3>>> from blog.models import Author
>>> joe = Author.objects.create(name="Joe")
>>> entry.authors.add(joe)
获取数据
从数据库中获取数据会生成一个 QuerySet 对象,它代表从数据库中取出的数据对象的集合。QuerySet 等同于数据库中的 SELECT 语句,它可以有零个或者多个 filter 。filter 对应于数据库中的筛选条件如 WHERE 或 LIMIT 等。
获取单个对象>>> one_entry = Entry.objects.get(pk=1)
PS:pk 即 primary key 。
获取所有对象>>> all_entries = Entry.objects.all()
应用筛选器>>> entry = Entry.objects.filter(pub_date__year=2006)
Limiting>>> entries = Entry.objects.all()[:5]
排序>>> entry = Entry.objects.order_by('headline')[0]
字段查询
字段查询对应于 SQL 中的 WHERE 语句,可以通过向 QuerySet 对象的方法 filter()、exclude() 和 get() 中传入特定的参数来实现。
基本的查询参数语法如下:field__lookuptype=value 。
如:>>> Entry.objects.filter(pub_date__lte='2006-01-01')
等同于:SELECT * FROM blog_entry WHERE pub_date <= '2006-01-01';
其中 lte 即 less or equal(小于等于)。其他类似的 lookuptype 还包括 gt(大于)、gte(大于等于)、lt(小于)、exact、iexact(忽略大小写)、startswith、istartswith、endswith、iendswith、contains、range(指定范围)、regex(正则表达式)、iregex 等。
以下为一些常见的使用示例:
exact:
>>> Entry.objects.get(headline__exact="Cat bites dog")
等于SELECT ... WHERE headline = 'Cat bites dog';iexact:
>>> Blog.objects.get(name__iexact="beatles blog")
等于SELECT ... WHERE name ILIKE 'beatles blog';startswith:
>>> Entry.objects.filter(headline__startswith='Lennon')
等于SELECT ... WHERE headline LIKE 'Lennon%';contains:
>>> Entry.objects.get(headline__contains='Lennon')
等于SELECT ... WHERE headline LIKE '%Lennon%';in:
>>> Entry.objects.filter(id__in=[1, 3, 4])
等于SELECT ... WHERE id IN (1, 3, 4);range:
1
2
3
4import datetime
start_date = datetime.date(2005, 1, 1)
end_date = datetime.date(2005, 3, 31)
Entry.objects.filter(pub_date__range=(start_date, end_date))
等于 SELECT ... WHERE pub_date BETWEEN '2005-01-01' and '2005-03-31';
Manager
Manager 是提供给 Django 模型,用于做数据库查询操作的接口。Django 项目中的每一个模型都需要至少包含一个 Manager 对象。
默认情况下,Django 会在每一个模型类中添加一个名为 objects 的 Manager 。通过将 models.Manager() 赋值给除 objects 以外的类属性,可以覆盖此默认行为:1
2
3
4
5from django.db import models
class Person(models.Model):
#...
people = models.Manager()
此时 Person.objects.all() 查询语句会报出 AttributeError 错误,而 Person.people.all() 则返回所有的 Person 对象。
自定义 Manager
自定义的 Manager 方法可以向模型中添加表级别的查询功能。与之对应的纪录级别的功能则需要使用模型方法。
如:1
2
3
4
5
6
7
8
9
10
11
12# First, define the Manager subclass.
class DahlBookManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(author='Roald Dahl')
# Then hook it into the Book model explicitly.
class Book(models.Model):
title = models.CharField(max_length=100)
author = models.CharField(max_length=50)
objects = models.Manager() # The default manager.
dahl_objects = DahlBookManager() # The Dahl-specific manager.
以上面的模型为例,Book.objects.all() 会返回数据库中所有的书籍信息,而 Book.dahl_objects.all() 则会返回所有作者为 Roald Dahl 的书籍。
Django 允许向模型中添加任意数量的 Manager() 实例,因此可以用来为模型定义一些通用的筛选器。如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class AuthorManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(role='A')
class EditorManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(role='E')
class Person(models.Model):
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
role = models.CharField(max_length=1, choices=[('A', _('Author')), ('E', _('Editor'))])
people = models.Manager()
authors = AuthorManager()
editors = EditorManager()
则 Person.people.all()、Person.authors.all() 和 Person.editors.all() 都可以作为从模型中获取数据的接口,且 authors 与 editors 已预先根据 role 对数据进行了筛选。