5. 让 django 完成翻译——迁移数据库模型

本教程内容已过时,更新版教程请访问: Django 博客开发入门教程

这是 Django 博客教程的第 5 篇,在阅读此篇教程以前,请确保你已阅读 Django 博客教程的前 4 篇:
1. Django 博客教程:前言
2. 搭建开发环境
3. 建立我们的 django 博客应用
4. 创建 django 博客的数据库模型

上一篇教程中我们完成了数据库模型的代码,但是那还只是 Python 代码而已,我们还没有让 django 把它翻译成数据库语言,因此实际上这些数据库表格没有真正地创建。

为了让 django 完成翻译,帮我们创建好这些数据库表,我们再一次请出我的工程管理助手 manage.py。进入虚拟环境,切换到 manage.py 文件所在的目录下,分别运行 python manage.py makemigrationspython manage.py migrate 命令:

C:\WINDOWS\system32>C:\Users\yangxg\Envs\blogproject_env\Scripts\activate

(blogproject_env) C:\WINDOWS\system32>cd C:\Users\yangxg\Workspace\blogproject

(blogproject_env) C:\Users\yangxg\Workspace\blogproject>python manage.py makemigrations
Migrations for 'blog':
  blog\migrations\0001_initial.py:
    - Create model Category
    - Create model Post
    - Create model Tag
    - Add field tags to post
(blogproject_env) C:\Users\yangxg\Workspace\blogproject>python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying contenttypes.0001_initial... OK
  Applying auth.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying blog.0001_initial... OK
  Applying sessions.0001_initial... OK

可以看到,当我们执行了 python manage.py makemigrations 后,django 在 blog 应用的 migrations 目录下生成了一个 0001_initial.py 文件,这个文件是 django 用来记录我们对模型做了哪些修改的文件。目前来说,我们在 models.py 文件里创建了 3 个模型类,django 把这些变化记录在了 0001_initial.py 里。但此时只是告诉了 django 我们做了哪些改变,为了让 django 真正地为我们创建数据库表,接下来又执行了 python manage.py migrate 命令。django 通过检测应用中 migrations 目录下的文件,得知我们对数据库做了哪些操作,然后它把这些操作翻译成数据库语言,从而把这些操作应用于真正的数据库里。你可以看到命令的输出除了 Applying blog.0001_initial... OK 外,django 还对其他文件做了操作。这是因为除了我们自己建立的 blog 应用外,django 自身还内置了很多应用,这些应用自己也是需要数据的。可以在 settings.py 的 INSTALLED_APP 设置里看到这些应用:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
]

当然我们目前不必关心这些。

写给了解数据库语言的人:你可以运行下面的命令看看 django 究竟为我们做了什么:

python manage.py sqlmigrate blog 0001

你将看到输出的经 django 翻译后的数据库表创建语句,这有助于你理解 django 的 ORM 的工作机制。

使用 django 的方式从数据库里获取数据

数据库最主要的操作就是往里面存入数据、从中取出数据、修改已保存的数据和删除不再需要的数据。和创建数据库表一样,django 为这些操作提供了一整套方法,从而把我们从数据库中解放出来。我们不用学习如何利用数据库语言去完成这些操作,只要简单地调用几个 python 函数就可以满足我们的需求。

先在命令行中来探索一下这些函数,感受一下如何使用 django 的方式来操作数据库。在 manage.py 所在目录下运行命令:

(blogproject_env) C:\Users\yangxg\Workspace\blogproject>python manage.py shell
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>>

这打开了一个交互式命令行。

首先我们来创建一个分类和一个标签:

>>> from blog.models import Category, Tag, Post
>>> c = Category(name='category test')
>>> c.save()
>>> t = Tag(name='tag test')
>>> t.save()

我们首先导入 3 个我们之前写好的模型类,然后实例化了一个 Category 类和一个 Tag 类,为他们的属性 name 赋了值。为了让 django 帮我们把这些数据保存进数据库,调用实例的 save() 方法即可。

再创建一篇文章试试,但创建文章之前,我们需要先创建一个 User,用于指定文章的作者,创建 User 的命令 django 已经帮我们写好了,依然是通过 manage.py 来运行。首先按住 Ctrl + c 退出命令交互栏,运行下面的命令并根据提示创建用户:

(blogproject_env) C:\Users\yangxg\Workspace\blogproject>python manage.py createsuperuser
Username (leave blank to use 'zmrenwu@163.com'): myuser
Email address: a@aa.com
Password:
Password (again):
Superuser created successfully.

运行 python manage.py createsuperuser 开始创建用户,之后会提示你输入用户名、邮箱、密码和确认密码,按照提示输入即可。注意一点的是密码输入过程中不会有任何字符显示,不要误以为你的键盘出问题了。最后出现 Superuser created successfully. 说明用户创建成功了。

再次进入 Python 命令交互栏,开始创建文章:

>>> from blog.models import Category, Tag, Post
>>> from django.utils import timezone
>>> from django.contrib.auth.models import User

>>> user = User.objects.get(username='myuser')
>>> c = Category.objects.get(name='category test')

>>> p = Post(title='title test', body='body test', created_time=timezone.now(), modified_time=timezone.now(), category=c, author=user)
>>> p.save()

由于我们重启了 shell,因此我们重新导入了 Category, Tag, Post 以及 User,我们还导入了一个 django 提供的辅助模块 timezone,这是因为我们需要调用它的 now() 方法为 created_time 和 modified_time 指定时间。然后我们根据用户名和分类名,通过 get 方法取出了存在数据库中的 User 和 Category(取数据将在下面介绍)。此外还指定 title,body 的值,并把它和前面创建的 category c 关联了起来,只有允许为空 excerpt、tags 我们就没有为他们指定值了。

数据已经存入数据库了,现在要把它们取出来看看:

>>> Category.objects.all()
<QuerySet [<Category: Category object>]>
>>> Tag.objects.all()
<QuerySet [<Tag: Tag object>]>
>>> Post.objects.all()
<QuerySet [<Post: Post object>]>
>>>

objects 是我们的模型管理器,它为我们提供一系列从数据库中取数据方法,这里我们使用了 all() 方法,表示我们要把对应的数据全部取出来。可以看到 all() 方法都返回了数据,这些数据应该是我们之前存进去的,但是显示的字符串有点奇怪,让人看不懂。为了让显示出来的数据更加人性化一点,我们分别为三个模型增加一个 __str__() 方法:

from django.utils.six import python_2_unicode_compatible

@python_2_unicode_compatible
class Category(models.Model):
    ...

    def __str__(self):
        # python_2_unicode_compatible 装饰器用于兼容 Python2
        return self.name
    
@python_2_unicode_compatible
class Tag(models.Model):
    ...

    def __str__(self):
        # python_2_unicode_compatible 装饰器用于兼容 Python2
        return self.name
    
@python_2_unicode_compatible
class Post(models.Model):
    ...
    
    def __str__(self):
        # python_2_unicode_compatible 装饰器用于兼容 Python2
        return self.title

定义好 __str__() 方法后,解释器显示的内容将会是由 __str__() 方法返回的内容,这里 Category 返回分类名 name,Tag 返回标签名,而 Post 返回它的 title。

先按 Ctrl + C 退出 shell,再重新运行 python manage.py shell 进入 shell。

>>> from blog.models import Category, Tag, Post
>>> Category.objects.all()
<QuerySet [<Category: category test>]>

>>> Tag.objects.all()
<QuerySet [<Tag: tag test>]>

>>> Post.objects.all()
<QuerySet [<Post: title test>]>

可以看到返回的是我们之前存入的数据。此外我们在创建文章时提到了通过 get 方法来获取数据,这里 all 方法和 get 方法的区别是,all 返回全部数据,是一个列表,而 get 返回一条记录数据。

尝试修改数据:

>>> c = Category.objects.get(name='category test')
>>> c.name = 'category test new'
>>> c.save()
>>> Category.objects.all()
<QuerySet [<Category: test category new>]>

首先通过 get 方法根据分类名 name 获取到分类,修改它的 name 属性为新的值 category test new,然后调用 save 方法把修改保存到数据库,之后可以看到数据库返回的数据已经是修改后的值了。Tag、Post 的修改也一样。

删除掉数据:

>>> p = Post.objects.get(title='title test')
>>> p
<Post: title test>
>>> p.delete()
(1, {'blog.Post_tags': 0, 'blog.Post': 1})
>>> Post.objects.all()
<QuerySet []>

先根据标题 title 从数据库中取出 Post,保存在变量 p 中,然后调用它的 delete 方法,最后看到 Post.objects.all() 返回了一个空的 QuerySet(类似于一个列表),表明数据库中没有 Post,Post 已经被删除了。

这就是 django 对数据库增、删、改、查的操作。除了上述演示的方法外,django 还为我们提供了大量其它的方法,这些方法有一部分会在教程中使用,用到时我会讲解它们的用法。但以后你开发自己的项目时,你就需要通过阅读 django 的官方文档 来了解有哪些方法已经如何使用它们。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容