在上一部分已经完成一个简单的 视图和请求响应的编写。
Django采用类似MVC架构的MVT模型
今天主要学习一下model(模型)部分,模式指的是定义数据库相关的内容,一般放在models.py文件中。
模型是后台数据唯一的准确来源,它包含数据部分和数据的行为。一般来说,每一个模型都映射一个数据库表。
基础:
- 每个模型都是一个Python的类,这些类继承
django.db.models.Model
- 模型类的每个属性都相当于一个数据库的细分。
- 综诉说,Django给你一个自动生成访问数据库的API;
模型因为是定义数据库相关的内容、所以得先在Django框架中进行数据库的配置。
一、数据库配置
Django的数据库配置在项目的配置文件目录下的settings.py中,即mysite/mysite/settings.py中。
settings.py前面有说过是整个项目的配置中心。Django默认使用的SQLite数据库,因为Python源生支持SQLite数据库,如果使用默认数据库的话也不需要安装其他的库。
在settings.py中数据库部分如下:
如果使用其他数据库的话、需要进行一些配置和安装创建操作
安装所要使用的数据库类型的操作模块,修改DATABASES中default
的键值。
-
ENGINE(引擎):
django.db.backends.sqlite3
、django.db.backends.postgresql
、django.db.backends.mysql
、django.db.backends.oracle
,等等。 -
NAME(名称):数据库名称,如果使用的是默认的SQLite,那么数据库将作为一个文件存放于本机,此时的NAME应该是这个文件的完整绝对路径包括文件名,默认值
os.path.join(BASE_DIR, ’db.sqlite3’)
,将把该文件储存在你的项目目录下。
下面举一个mysql的配置例子:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME':'test',
'HOST':'localhost',
'USER':'root',
'PASSWORD':'123456',
'PORT':'3306',
}
}
- 在使用非SQLite的数据库时,需要先对数据库进行创建。
- settings文件中所配置的数据库用户也应该具有响应的权限,比如建表,增删改查等。
下面说一下settings中的其他必要设置
1、TIME_ZONE
设置为国内所在的时区Asia/Shanghai
。根据需要去配置不同时区。
2、settings文件中顶部的INSTALLED_APPS
设置项。代表所有项目中被激活的Django应用(app)。如果要启用自建的app的话、需要在这里进行注册。前面说过一个项目可以有多个app,一个app也可以被多个项目使用。
INSTALLED_APPS
中会自动包含以下默认条目,它们都是Django自动生成的:
- django.contrib.admin:admin管理后台站点
- django.contrib.auth:身份认证系统
- django.contrib.contenttypes:内容类型框架
- django.contrib.sessions:会话框架
- django.contrib.messages:消息框架
- django.contrib.staticfiles:静态文件管理框架
上面的一些应用也需要建立一些数据库表,所以在使用它们之前我们要在数据库中创建这些表。使用下面的命令创建数据表:
$ python manage.py migrate
migrate命令将遍历INSTALLED_APPS
设置中的所有项目,在数据库中创建对应的表,并打印出每一条动作信息。
同时也可以在你配置的数据库中去详细查看创建出来的表。
二、创建模型
Django通过自定义Python类的形式来定义具体的模型,每个模型的物理存在方式就是一个Python的类Class,每个模型代表数据库中的一张表,每个类的实例代表数据表中的一行数据,类中的每个变量代表数据表中的一列字段。Django通过模型,将Python代码和数据库操作结合起来,实现对SQL查询语言的封装。Django通过ORM对数据库进行操作,奉行代码优先的理念,将Python程序员和数据库管理员进行分工解耦。
按照投票应用的需求,建立两个模型:Question
和Choice,问题和选项。
Question包含一个问题和一个发布日期。Choice包含两个字段:该选项的文本描述和该选项的投票数。每一条Choice都关联到一个Question。这些都是由Python的类来体现,编写的全是Python的代码,不接触任何SQL语句。现在,编辑polls/models.py
文件,具体代码如下:
# polls/models.py
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
每一个类都是django.db.models.Model
的子类。每一个字段都是Field
类的一个实例,例如用于保存字符数据的CharField和用于保存时间类型的DateTimeField,它们告诉Django每一个字段保存的数据类型。
每一个Field实例的名字就是字段的名字(如: question_text 或者 pub_date )。在你的Python代码中会使用这个值,你的数据库也会将这个值作为表的列名。
你也可以在每个Field中使用一个可选的第一位置参数用于提供一个人类可读的字段名,让你的模型更友好,更易读,并且将被作为文档的一部分来增强代码的可读性。例如 发布日期。
一些Field类必须提供某些特定的参数。例如CharField需要你指定max_length。这不仅是数据库结构的需要,同样也用于数据验证功能。
有必填参数,当然就会有可选参数,比如在votes里我们将其默认值设为0.
最后请注意,我们使用ForeignKey
定义了一个外键关系。它告诉Django,每一个Choice关联到一个对应的Question(注意要将外键写在‘多’的一方)。Django支持通用的数据关系:一对一,多对一和多对多。
三、使用模型
第一步首先要把我们要启用的app在项目中进行注册,这样migrate才会创建我们注册了的app的数据库表和模型的api。
- 创建该app对应的数据库表结构
- 为Question和Choice对象创建基于Python的数据库访问API
在settings中进行APP的注册、顺便把时区改一下。
要将应用添加到项目中,需要在INSTALLED_APPS
设置中增加指向该应用的配置文件的链接。对于本例的投票应用,它的配置类文件PollsConfig是polls/apps.py
,所以它的点式路径为polls.apps.PollsConfig
。我们需要在INSTALLED_APPS
中,将该路径添加进去:
这里看到说还可以直接简写为“polls”,这个我也没试过,初学还是严谨一点写全乎吧。
顺便把时区从UTC改掉、
TIME_ZONE = 'UTC' ====》 TIME_ZONE = 'Asia/Shanghai'
因为我们之前已经进行了一次模型的数据库表操作,这次我们新加入了一个应用的模型。
我们需要执行一个命令,进行模型的改动、并且会先把改动内容记录下来、最简单的解释就是 生成了改动脚本
$ python manage.py makemigrations polls
通过运行makemigrations
命令,Django 会检测你对模型文件的修改,也就是告诉Django你对模型有改动,并且你想把这些改动保存为一个“迁移(migration)”。(简单理解为数据库归档)migrations
是Django保存模型修改记录的文件,这些文件保存在磁盘上。在例子中,它就是polls/migrations/0001_initial.py
,你可以打开它看看,里面保存的都是人类可读并且可编辑的内容,方便你随时手动修改。
执行命令后得到下面的结果
生成了要执行的修改脚本、创建了两个模型。
我们可以先看看脚本
$ python manage.py sqlmigrate polls 0001
打印出来了脚本内容、图我没截全
- 实际的输出内容将取决于使用的数据库会有所不同。上面的是默认的SQLite库的的输出。
- 表名是自动生成的,通过组合应用名 (polls) 和小写的模型名
question
和choice
。 ( 你可以重写此行为。) - 主键 (ID) 是自动添加的。( 你也可以重写此行为。)
- 按照惯例,Django 会在外键字段名上附加 "_id" 。 (你仍然可以重写此行为。)
- 生成SQL语句时针对你所使用的数据库,会为你自动处理特定于数据库的字段,例如 auto_increment (MySQL), serial (PostgreSQL), 或integer primary key (SQLite) 。 在引用字段名时也是如此 – 比如使用双引号或单引号。
- 这些SQL命令并没有在你的数据库中实际运行,它只是在屏幕上显示出来,以便让你了解Django真正执行的是什么。
这次就按照自动生成的去执行、后面如果正式项目的话、比如就需要对主键名称、以及外建的名称等去做修改
下面可以继续去按照最新的修改去执行了
$ python manage.py migrate
migrate命令对所有还未实施的迁移记录进行操作,本质上就是将你对模型的修改体现到数据库中具体的表上面。Django通过一张叫做django_migrations的表,记录并跟踪已经实施的migrate动作,通过对比获得哪些migrations尚未提交。
migrations的功能非常强大,允许你随时修改你的模型,而不需要删除或者新建你的数据库或数据表,在不丢失数据的同时,实时动态更新数据库。我们将在后面的章节对此进行深入的阐述,但是现在,只需要记住修改模型时的操作分三步:
- 在models.py中修改模型;
- 运行
python manage.py makemigrations,
为改动创建迁移记录; - 运行
python manage.py migrate
,将操作同步到数据库。
之所以要将创建和实施迁移的动作分成两个命令两步走是因为你也许要通过版本控制系统(例如github,svn)提交你的项目代码,如果没有一个中间过程的保存文件(migrations),那么github如何知道以及记录、同步、实施你所进行过的模型修改动作呢?毕竟,github不和数据库直接打交道,也没法和你本地的数据库通信。但是分开之后,你只需要将你的migration文件(例如上面的0001)上传到github,它就会知道一切。
四、通过模型API进行操作
进入python交互环境。
可以执行命令进入
python manage.py shell
相比较直接输入“python”命令的方式进入Python环境,调用manage.py
参数能将DJANGO_SETTINGS_MODULE
环境变量导入,它将自动按照mysite/settings.py
中的设置,配置好你的python shell环境,这样,你就可以导入和调用任何你项目内的模块了。
或者你也可以这样,先进入一个纯净的python shell环境,然后启动Django,具体如下:
>>> import django
>>> django.setup()
我这里是直接在pycharm中的Python交互环境来执行的
下面记录一下练习的内容:
>>> from polls.models import Question, Choice # 导入我们写的模型类
# 现在系统内还没有questions对象
>>> Question.objects.all()
<QuerySet []>
# 创建一个新的question对象
# Django推荐使用timezone.now()代替python内置的datetime.datetime.now()
# 这个timezone就来自于Django的依赖库pytz
from django.utils import timezone
>>> q = Question(question_text="What's new?", pub_date=timezone.now())
# 你必须显式的调用save()方法,才能将对象保存到数据库内
>>> q.save()
# 默认情况,你会自动获得一个自增的名为id的主键
>>> q.id
1
# 通过python的属性调用方式,访问模型字段的值
>>> q.question_text
"What's new?"
>>> q.pub_date
datetime.datetime(2012, 2, 26, 13, 0, 0, 775217, tzinfo=<UTC>)
# 通过修改属性来修改字段的值,然后显式的调用save方法进行保存。
>>> q.question_text = "What's up?"
>>> q.save()
# objects.all() 用于查询数据库内的所有questions
>>> Question.objects.all()
<QuerySet [<Question: Question object>]>
上面的<Question: Question object>
是一个不可读的内容展示,无法明确的看出来这个类的具体内容,我们可以给模型中的两个类添加str方法、让Django在打印对象时显示一些我们指定的信息。
返回polls/models.py
文件,修改一下question和Choice这两个类,代码如下:
from django.db import models
class Question(models.Model):
# ...
def __str__(self):
return self.question_text
class Choice(models.Model):
# ...
def __str__(self):
return self.choice_text
在使用Django的admin站点时也同样有帮助。
再在Question
类中添加一个判断是否为近期发布的方法
import datetime
from django.db import models
from django.utils import timezone
class Question(models.Model):
# ...
def was_published_recently(self):
# 发布时间大于当前时间的前一天(一天内发布的)
return self.pub_date >= timezone.now() - datetime.timedelta(days=1)
继续进行一些练习:
>>> from polls.models import Question, Choice
# 先看看__str__()的效果,直观多了吧?
>>> Question.objects.all()
<QuerySet [<Question: What's up?>]>
# Django提供了大量的关键字参数查询API
>>> Question.objects.filter(id=1)
<QuerySet [<Question: What's up?>]>
>>> Question.objects.filter(question_text__startswith='What')
<QuerySet [<Question: What's up?>]>
# 获取今年发布的问卷
>>> from django.utils import timezone
>>> current_year = timezone.now().year
>>> Question.objects.get(pub_date__year=current_year)
<Question: What's up?>
# 查询一个不存在的ID,会弹出异常
>>> Question.objects.get(id=2)
Traceback (most recent call last):
...
DoesNotExist: Question matching query does not exist.
# Django为主键查询提供了一个缩写:pk。下面的语句和Question.objects.get(id=1)效果一样.
>>> Question.objects.get(pk=1)
<Question: What's up?>
# 看看我们自定义的方法用起来怎么样
>>> q = Question.objects.get(pk=1)
>>> q.was_published_recently()
True
# 让我们试试主键查询
>>> q = Question.objects.get(pk=1)
# 显示所有与q对象有关系的choice集合,目前是空的,还没有任何关联对象。
>>> q.choice_set.all()
<QuerySet []>
# 创建3个choices.
>>> q.choice_set.create(choice_text='Not much', votes=0)
<Choice: Not much>
>>> q.choice_set.create(choice_text='The sky', votes=0)
<Choice: The sky>
>>> c = q.choice_set.create(choice_text='Just hacking again', votes=0)
# Choice对象可通过API访问和他们关联的Question对象
>>> c.question
<Question: What's up?>
# 同样的,Question对象也可通过API访问关联的Choice对象
>>> q.choice_set.all()
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
>>> q.choice_set.count()
3
# API会自动进行连表操作,通过双下划线分割关系对象。连表操作可以无限多级,一层一层的连接。
# 下面是查询所有的Choices,它所对应的Question的发布日期是今年。(重用了上面的current_year结果)
>>> Choice.objects.filter(question__pub_date__year=current_year)
<QuerySet [<Choice: Not much>, <Choice: The sky>, <Choice: Just hacking again>]>
# 使用delete方法删除对象
>>> c = q.choice_set.filter(choice_text__startswith='Just hacking')
>>> c.delete()
这部分内容是Django项目的核心,也是动态网站与数据库交互的核心,对于初学者,再难理解也要理解。