百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

第 04 篇:Django 迁移、操作数据库

bigegpt 2024-10-28 12:51 5 浏览

文中涉及的示例代码,已同步更新到 HelloGitHub-Team 仓库[1]

我们已经编写了博客数据库模型的代码,但那还只是 Python 代码而已,django 还没有把它翻译成数据库语言,因此实际上这些数据库表还没有真正的在数据库中创建。

迁移数据库

为了让 django 完成翻译,创建好这些数据库表,我们再一次请出我的工程管理助手 manage.py。切换到 manage.py 文件所在的目录(项目根目录)下,分别运行 pipenv run python manage.py makemigrationspipenv run python manage.py migrate 命令:

> pipenv run python manage.py makemigrations
Migrations for 'blog': 
	blog\migrations\0001_initial.py 
	- Create model Category 
	- Create model Tag 
	- Create model Post 

> pipenv run 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 admin.0003_logentry_add_action_flag_choices... 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 auth.0009_alter_user_last_name_max_length... OK 
	Applying auth.0010_alter_group_name_max_length... OK 
	Applying auth.0011_update_proxy_permissions... 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 设置里看到这些应用,当然我们目前不必关心这些。

blogproject/settings.py

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

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

> pipenv run python manage.py sqlmigrate blog 0001

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

选择数据库版本

我们没有安装任何的数据库软件,django 就帮我们迁移了数据库。这是因为我们使用了 Python 内置的 SQLite3 数据库。

SQLite3 是一个十分轻巧的数据库,它仅有一个文件。你可以看一到项目根目录下多出了一个 db.sqlite3 的文件,这就是 SQLite3 数据库文件,django 博客的数据都会保存在这个数据库文件里。

django 在 settings.py 里为我们做了一些默认的数据库配置:

blogproject/settings.py

## 其它配置选项...
DATABASES = { 
	'default': { 
		'ENGINE': 'django.db.backends.sqlite3', 
		'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
	}
}
## 其它配置选项...

可以看到默认的数据库引擎就是使用的 SQLite3。

当然一些人倾向于使用 MySQL 等大型数据库,至于 django 如何配置 MySQL 这里就不赘述了,你可以自行使用搜索引擎或者查阅 django 的官方文档解决。对于一个小型博客而言,SQLite3 数据库足以胜任。

用 django 的方式操作数据库

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

存数据

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

> pipenv run python manage.py shell
Python 3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [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 退出命令交互栏(一次退不出就连续多按几次),运行 pipenv run python manage.py createsuperuser 命令并根据提示创建用户:

> pipenv run 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 manage.py shell 进入 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,因此需要重新导入了 CategoryTagPost 以及 User。我们还导入了一个 django 提供的辅助模块 timezone,这是因为我们需要调用它的 now() 方法为 created_timemodified_time 指定时间,容易理解 now 方法返回当前时间。然后我们根据用户名和分类名,通过 get 方法取出了存在数据库中的 UserCategory(取数据的方法将在下面介绍)。接着我们为文章指定了 titlebodycreated_timemodified_time值,并把它和前面创建的 Category 以及 User 关联了起来。允许为空 excerpttags 我们就没有为它们指定值了。

注意

我们这里使用 get 方法根据 Categoryname 属性的值获取分类的一条记录。Category.objects.get(name='category test') 的含义是从数据库中取出 name 的值为 category test 的分类记录。确保数据库中只有一条值为 category test 的记录,否则 get 方法将返回一个 MultipleObjectsReturned 异常。如果你不小心已经存了多条记录,请删掉多余的记录。如何删除数据请看下文。

取数据

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

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

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

blog/models.py

class Category(models.Model): 
	...
	
	def __str__(self): 
		return self.name 

class Tag(models.Model): 
	... 

	def __str__(self): 
		return self.name 

class Post(models.Model):	
	... 
	
	def __str__(self): 
		return self.title

定义好 __str__ 方法后,解释器显示的内容将会是 __str__ 方法返回的内容。这里 Category 返回分类名 nameTag 返回标签名,而 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>]>

>>> Post.objects.get(title='title test')
<Post: title test>

可以看到返回的是我们之前存入的数据。

此外我们在创建文章时提到了通过 get 方法来获取数据,这里 all 方法和 get 方法的区别是:all 方法返回全部数据,是一个类似于列表的数据结构(QuerySet);而 get 返回一条记录数据,如有多条记录或者没有记录,get 方法均会抛出相应异常。

改数据

尝试修改数据:

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

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

删数据

删除掉数据:

>>> 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 的官方文档[2]来了解有哪些方法可用以及如何使用它们。

References

[1] HelloGitHub-Team 仓库: https://github.com/HelloGitHub-Team/HelloDjango-blog-tutorial

[2] django 的官方文档: https://docs.djangoproject.com/en/2.2/ref/models/querysets/

相关推荐

悠悠万事,吃饭为大(悠悠万事吃饭为大,什么意思)

新媒体编辑:杜岷赵蕾初审:程秀娟审核:汤小俊审签:周星...

高铁扒门事件升级版!婚宴上‘冲喜’老人团:我们抢的是社会资源

凌晨两点改方案时,突然收到婚庆团队发来的视频——胶东某酒店宴会厅,三个穿大红棉袄的中年妇女跟敢死队似的往前冲,眼瞅着就要扑到新娘的高额钻石项链上。要不是门口小伙及时阻拦,这婚礼造型团队熬了三个月的方案...

微服务架构实战:商家管理后台与sso设计,SSO客户端设计

SSO客户端设计下面通过模块merchant-security对SSO客户端安全认证部分的实现进行封装,以便各个接入SSO的客户端应用进行引用。安全认证的项目管理配置SSO客户端安全认证的项目管理使...

还在为 Spring Boot 配置类加载机制困惑?一文为你彻底解惑

在当今微服务架构盛行、项目复杂度不断攀升的开发环境下,SpringBoot作为Java后端开发的主流框架,无疑是我们手中的得力武器。然而,当我们在享受其自动配置带来的便捷时,是否曾被配置类加载...

Seata源码—6.Seata AT模式的数据源代理二

大纲1.Seata的Resource资源接口源码2.Seata数据源连接池代理的实现源码3.Client向Server发起注册RM的源码4.Client向Server注册RM时的交互源码5.数据源连接...

30分钟了解K8S(30分钟了解微积分)

微服务演进方向o面向分布式设计(Distribution):容器、微服务、API驱动的开发;o面向配置设计(Configuration):一个镜像,多个环境配置;o面向韧性设计(Resista...

SpringBoot条件化配置(@Conditional)全面解析与实战指南

一、条件化配置基础概念1.1什么是条件化配置条件化配置是Spring框架提供的一种基于特定条件来决定是否注册Bean或加载配置的机制。在SpringBoot中,这一机制通过@Conditional...

一招解决所有依赖冲突(克服依赖)

背景介绍最近遇到了这样一个问题,我们有一个jar包common-tool,作为基础工具包,被各个项目在引用。突然某一天发现日志很多报错。一看是NoSuchMethodError,意思是Dis...

你读过Mybatis的源码?说说它用到了几种设计模式

学习设计模式时,很多人都有类似的困扰——明明概念背得滚瓜烂熟,一到写代码就完全想不起来怎么用。就像学了一堆游泳技巧,却从没下过水实践,很难真正掌握。其实理解一个知识点,就像看立体模型,单角度观察总...

golang对接阿里云私有Bucket上传图片、授权访问图片

1、为什么要设置私有bucket公共读写:互联网上任何用户都可以对该Bucket内的文件进行访问,并且向该Bucket写入数据。这有可能造成您数据的外泄以及费用激增,若被人恶意写入违法信息还可...

spring中的资源的加载(spring加载原理)

最近在网上看到有人问@ContextConfiguration("classpath:/bean.xml")中除了classpath这种还有其他的写法么,看他的意思是想从本地文件...

Android资源使用(android资源文件)

Android资源管理机制在Android的开发中,需要使用到各式各样的资源,这些资源往往是一些静态资源,比如位图,颜色,布局定义,用户界面使用到的字符串,动画等。这些资源统统放在项目的res/独立子...

如何深度理解mybatis?(如何深度理解康乐服务质量管理的5个维度)

深度自定义mybatis回顾mybatis的操作的核心步骤编写核心类SqlSessionFacotryBuild进行解析配置文件深度分析解析SqlSessionFacotryBuild干的核心工作编写...

@Autowired与@Resource原理知识点详解

springIOCAOP的不多做赘述了,说下IOC:SpringIOC解决的是对象管理和对象依赖的问题,IOC容器可以理解为一个对象工厂,我们都把该对象交给工厂,工厂管理这些对象的创建以及依赖关系...

java的redis连接工具篇(java redis client)

在Java里,有不少用于连接Redis的工具,下面为你介绍一些主流的工具及其特点:JedisJedis是Redis官方推荐的Java连接工具,它提供了全面的Redis命令支持,且...