July's Blog

花有重开日,人无再少年

0%

django笔记04 - 数据库

Django配置连接数据库

settings.py文件中数据库相关的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DATABASES = {
'default': {
# 数据库引擎(是mysql还是oracle等)
'ENGINE': 'django.db.backends.mysql',
# 数据库的名字
'NAME': 'dfz',
# 连接mysql数据库的用户名
'USER': 'root',
# 连接mysql数据库的密码
'PASSWORD': 'root',
# mysql数据库的主机地址
'HOST': '127.0.0.1',
# mysql数据库的端口号
'PORT': '3306',
}
}

在Django中操作数据库

下面是使用原生sql语句操作

1
2
3
4
5
6
7
8
9
10
11
12
# 使用django封装好的connection对象,会自动读取settings.py中数据库的配置信息
from django.db import connection

# 获取游标对象
cursor = connection.cursor()
# 拿到游标对象后执行sql语句
cursor.execute("select * from book")
# 获取所有的数据
rows = cursor.fetchall()
# 遍历查询到的数据
for row in rows:
print(row)

以上的execute以及fetchall方法都是Python DB API规范中定义好的。任何使用Python来操作MySQL的驱动程序都应该遵循这个规范。所以不管是使用pymysql或者是mysqlclient或者是mysqldb,他们的接口都是一样的。

Python DB API下规范下cursor对象常用接口:

  1. description:如果cursor执行了查询的sql代码。那么读取cursor.description属性的时候,将返回一个列表,这个列表中装的是元组,元组中装的分别是(name,type_code,display_size,internal_size,precision,scale,null_ok),其中name代表的是查找出来的数据的字段名称,其他参数暂时用处不大。
  2. rowcount:代表的是在执行了sql语句后受影响的行数。
  3. close:关闭游标。关闭游标以后就再也不能使用了,否则会抛出异常。
  4. execute(sql[,parameters]):执行某个sql语句。如果在执行sql语句的时候还需要传递参数,那么可以传给parameters参数。示例代码如下:
1
cursor.execute("select * from article where id=%s",(1,))
  1. fetchone:在执行了查询操作以后,获取第一条数据。
  2. fetchmany(size):在执行查询操作以后,获取多条数据。具体是多少条要看传的size参数。如果不传size参数,那么默认是获取第一条数据。
  3. fetchall:获取所有满足sql语句的数据。

ORM模型的创建和映射:

创建ORM模型:

ORM模型一般都是放在appmodels.py文件中。每个app都可以拥有自己的模型。并且如果这个模型想要映射到数据库中,那么这个app必须要放在settings.pyINSTALLED_APP中进行安装。以下是写一个简单的书籍ORM模型。示例代码如下:

1
2
3
4
5
6
7
from django.db import models

class Book(models.Model):
name = models.CharField(max_length=20,null=False)
author = models.CharField(max_length=20,null=False)
pub_time = models.DateTimeField(default=datetime.now)
price = models.FloatField(default=0)

以上便定义了一个模型。这个模型继承自django.db.models.Model,如果这个模型想要映射到数据库中,就必须继承自这个类。这个模型以后映射到数据库中,表名是模型名称的小写形式,为book。在这个表中,有四个字段,一个为name,这个字段是保存的是书的名称,是varchar类型,最长不能超过20个字符,并且不能为空。第二个字段是作者名字类型,同样也是varchar类型,长度不能超过20个。第三个是出版时间,数据类型是datetime类型,默认是保存这本书籍的时间。第五个是这本书的价格,是浮点类型。
还有一个字段我们没有写,就是主键id,在django中,如果一个模型没有定义主键,那么将会自动生成一个自动增长的int类型的主键,并且这个主键的名字就叫做id

映射模型到数据库中:

ORM模型映射到数据库中,总结起来就是以下几步:

  1. settings.py中,配置好DATABASES,做好数据库相关的配置。
  2. app中的models.py中定义好模型,这个模型必须继承自django.db.models
  3. 将这个app添加到settings.pyINSTALLED_APP中。
  4. 在命令行终端,进入到项目所在的路径,然后执行命令python manage.py makemigrations来生成迁移脚本文件。
  5. 同样在命令行中,执行命令python manage.py migrate来将迁移脚本文件映射到数据库中。

ORM对数据库的基本操作

添加数据

只要使用ORM模型创建一个对象。然后再调用这个ORM模型的save方法就可以保存了。

1
2
book = Book(name='西游记',author='吴承恩',price=100)
book.save()

查找数据:

所有的查找工作都是使用模型上的objects属性来完成的。当然也可以自定义查询对象。这部分功能会在后面讲到。

  1. 根据主键进行查找:使用主键进行查找。可以使用objects.get方法。然后传递pk=xx的方式进行查找。示例代码如下:
1
book = Book.objects.get(pk=2)
  1. 根据其他字段进行查找:可以使用objects.filter方法进行查找。示例代码如下:
1
books = Book.objects.filter(name='三国演义')

使用filter方法返回来的是一个QuerySet对象。这个对象类似于列表。我们可以使用这个对象的first方法来获取第一个值。

删除数据

首先查找到对应的数据模型。然后再执行这个模型的delete方法即可删除。示例代码如下:

1
2
book = Book.objects.get(pk=1)
book.delete()

修改数据:

首先查找到对应的数据模型。然后修改这个模型上的属性的值。再执行save方法即可修改完成。示例代码如下:

1
2
3
book = Book.objects.get(pk=2)
book.price = 200
book.save()

常用Field笔记:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Article(models.Model):
# 如果想要使用自己定义的Field来作为主键,那么必须设置primary_key=True
id = models.BigAutoField(primary_key=True)
# 在定义字段的时候,如果没有指定null=True,那么默认情况下,null=False
# 就是不能为空
# 如果想要使用可以为null的BooleanField,那么应该使用NullBooleanField来代替
removed = models.NullBooleanField()
# CharField:如果是超过了254个字符,那么就不建议使用啦
# 就推荐使用TextField:longtext
title = models.CharField(max_length=200)
# auto_now_add:是在第一次添加数据进去的时候会自动获取当前的时间,创建时间
# auto_now:每次这个对象调用save方法的时候都会将当前的时间更新,更新时间
create_time = models.DateTimeField(auto_now=True)
  1. navie时间:不知道自己的时间表示的是哪个时区的。也就是不知道自己几斤几两。比较幼稚。
  2. aware时间:知道自己的时间表示的是哪个时区的。也就是比较清醒。

pytz库

专门用来处理时区的库。这个库会经常更新一些时区的数据,不需要我们担心。并且这个库在安装Django的时候会默认的安装。如果没有安装,那么可以通过pip install pytz的方式进行安装。

astimezone方法:

将一个时区的时间转换为另外一个时区的时间。这个方法只能被aware类型的时间调用。不能被navie类型的时间调用。
示例代码如下:

1
2
3
4
5
6
7
8
9
10
import pytz
from datetime import datetime
now = datetime.now() # 这是一个navie类型的时间
utc_timezone = pytz.timezone("UTC") # 定义UTC的时区对象
utc_now = now.astimezone(utc_timezone) # 将当前的时间转换为UTC时区的时间
>> ValueError: astimezone() cannot be applied to a naive datetime # 会抛出一个异常,原因就是因为navie类型的时间不能调用astimezone方法

now = now.replace(tzinfo=pytz.timezone('Asia/Shanghai'))
utc_now = now.astimezone(utc_timezone)
# 这时候就可以正确的转换。

replace方法

可以将一个时间的某些属性进行更改。

django.utils.timezone.now方法

会根据settings.py中是否设置了USE_TZ=True获取当前的时间。如果设置了,那么就获取一个aware类型的UTC时间。如果没有设置,那么就会获取一个navie类型的时间。

django.utils.timezone.localtime方法

会根据setting.py中的TIME_ZONE来将一个aware类型的时间转换为TIME_ZONE指定时区的时间。
html模板中,会自动转换为当前时区时间。views中可以使用localtime转换为当前时区

DateField:

日期类型。在Python中是datetime.date类型,可以记录年月日。在映射到数据库中也是date类型。使用这个Field可以传递以下几个参数:

  1. auto_now:在每次这个数据保存的时候,都使用当前的时间。比如作为一个记录修改日期的字段,可以将这个属性设置为True
  2. auto_now_add:在每次数据第一次被添加进去的时候,都使用当前的时间。比如作为一个记录第一次入库的字段,可以将这个属性设置为True

DateTimeField:

日期时间类型,类似于DateField。不仅仅可以存储日期,还可以存储时间。映射到数据库中是datetime类型。这个Field也可以使用auto_nowauto_now_add两个属性。

TimeField:

时间类型。在数据库中是time类型。在Python中是datetime.time类型。

EmailField:

类似于CharField。在数据库底层也是一个varchar类型。最大长度是254个字符。

FileField:

用来存储文件的。这个请参考后面的文件上传章节部分。

ImageField:

用来存储图片文件的。这个请参考后面的图片上传章节部分。

FloatField:

浮点类型。映射到数据库中是float类型。

IntegerField:

整形。值的区间是-2147483648——2147483647

BigIntegerField:

大整形。值的区间是-9223372036854775808——9223372036854775807

PositiveIntegerField:

正整形。值的区间是0——2147483647

SmallIntegerField:

小整形。值的区间是-32768——32767

PositiveSmallIntegerField:

正小整形。值的区间是0——32767

TextField:

大量的文本类型。映射到数据库中是longtext类型。

UUIDField:

只能存储uuid格式的字符串。uuid是一个32位的全球唯一的字符串,一般用来作为主键。

URLField:

类似于CharField,只不过只能用来存储url格式的字符串。并且默认的max_length是200。

Field常用的参数

null:

如果设置为TrueDjango将会在映射表的时候指定是否为空。默认是为False。在使用字符串相关的Field(CharField/TextField)的时候,官方推荐尽量不要使用这个参数,也就是保持默认值False。因为Django在处理字符串相关的Field的时候,即使这个Fieldnull=False,如果你没有给这个Field传递任何值,那么Django也会使用一个空的字符串""来作为默认值存储进去。因此如果再使用null=TrueDjango会产生两种空值的情形(NULL或者空字符串)。如果想要在表单验证的时候允许这个字符串为空,那么建议使用blank=True。如果你的FieldBooleanField,那么对应的可空的字段则为NullBooleanField

blank:

标识这个字段在表单验证的时候是否可以为空。默认是False
这个和null是有区别的,null是一个纯数据库级别的。而blank是表单验证级别的。

db_column:

这个字段在数据库中的名字。如果没有设置这个参数,那么将会使用模型中属性的名字。

default:

默认值。可以为一个值,或者是一个函数,但是不支持lambda表达式。并且不支持列表/字典/集合等可变的数据结构。

primary_key:

是否为主键。默认是False

unique:

在表中这个字段的值是否唯一。一般是设置手机号码/邮箱等。

模型中Meta配置

对于一些模型级别的配置。我们可以在模型中定义一个类,叫做Meta。然后在这个类中添加一些类属性来控制模型的作用。比如我们想要在数据库映射的时候使用自己指定的表名,而不是使用模型的名称。那么我们可以在Meta类中添加一个db_table的属性。示例代码如下:

1
2
3
4
5
6
class Book(models.Model):
name = models.CharField(max_length=20,null=False)
desc = models.CharField(max_length=100,name='description',db_column="description1")

class Meta:
db_table = 'book_model'

以下将对Meta类中的一些常用配置进行解释。

db_table:

这个模型映射到数据库中的表名。如果没有指定这个参数,那么在映射的时候将会使用模型名来作为默认的表名。

ordering:

设置在提取数据的排序方式。后面章节会讲到如何查找数据。比如我想在查找数据的时候根据添加的时间排序,那么示例代码如下:

1
2
3
4
5
6
7
8
class Book(models.Model):
name = models.CharField(max_length=20,null=False)
desc = models.CharField(max_length=100,name='description',db_column="description1")
pub_date = models.DateTimeField(auto_now_add=True)

class Meta:
db_table = 'book_model'
ordering = ['pub_date'] #['-pub_date']是反向排序

外键和表关系

外键

MySQL中,表有两种引擎,一种是InnoDB,另外一种是myisam。如果使用的是InnoDB引擎,是支持外键约束的。外键的存在使得ORM框架在处理表关系的时候异常的强大。因此这里我们首先来介绍下外键在Django中的使用。

类定义为class ForeignKey(to,on_delete,**options)。第一个参数是引用的是哪个模型,第二个参数是在使用外键引用的模型数据被删除了,这个字段该如何处理,比如有CASCADESET_NULL等。这里以一个实际案例来说明。比如有一个User和一个Article两个模型。一个User可以发表多篇文章,一个Article只能有一个Author,并且通过外键进行引用。那么相关的示例代码如下:

1
2
3
4
5
6
7
8
class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)

class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey("User",on_delete=models.CASCADE) #外键

以上使用ForeignKey来定义模型之间的关系。即在article的实例中可以通过author属性来操作对应的User模型。这样使用起来非常的方便。示例代码如下:

1
2
3
4
5
6
7
8
article = Article(title='abc',content='123')
author = User(username='张三',password='111111')
article.author = author
article.save()

# 修改article.author上的值
article.author.username = '李四'
article.save()

为什么使用了ForeignKey后,就能通过author访问到对应的user对象呢。因此在底层,DjangoArticle表添加了一个属性名_id的字段(比如author的字段名称是author_id),这个字段是一个外键,记录着对应的作者的主键。以后通过article.author访问的时候,实际上是先通过author_id找到对应的数据,然后再提取User表中的这条数据,形成一个模型。

如果想要引用另外一个app的模型,那么应该在传递to参数的时候,使用app.model_name进行指定。以上例为例,如果UserArticle不是在同一个app中,那么在引用的时候的示例代码如下:

1
2
3
4
5
6
7
8
9
10
# User模型在user这个app中
class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)

# Article模型在article这个app中
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey("user.User",on_delete=models.CASCADE) #引用时指定所在app,app.model_name

如果模型的外键引用的是本身自己这个模型,那么to参数可以为self,或者是这个模型的名字。在论坛开发中,一般评论都可以进行二级评论,即可以针对另外一个评论进行评论,那么在定义模型的时候就需要使用外键来引用自身。示例代码如下:

1
2
3
4
5
class Comment(models.Model):
content = models.TextField()
origin_comment = models.ForeignKey('self',on_delete=models.CASCADE,null=True)
# 或者
# origin_comment = models.ForeignKey('Comment',on_delete=models.CASCADE,null=True)

外键删除操作

如果一个模型使用了外键。那么在对方那个模型被删掉后,该进行什么样的操作。可以通过on_delete来指定。可以指定的类型如下:

  1. CASCADE:级联操作。如果外键对应的那条数据被删除了,那么这条数据也会被删除。
  2. PROTECT:受保护。即只要这条数据引用了外键的那条数据,那么就不能删除外键的那条数据。
  3. SET_NULL:设置为空。如果外键的那条数据被删除了,那么在本条数据上就将这个字段设置为空。如果设置这个选项,前提是要指定这个字段可以为空。
  4. SET_DEFAULT:设置默认值。如果外键的那条数据被删除了,那么本条数据上就将这个字段设置为默认值。如果设置这个选项,前提是要指定这个字段一个默认值。
  5. SET():如果外键的那条数据被删除了。那么将会获取SET函数中的值来作为这个外键的值。SET函数可以接收一个可以调用的对象(比如函数或者方法),如果是可以调用的对象,那么会将这个对象调用后的结果作为值返回回去。
  6. DO_NOTHING:不采取任何行为。一切全看数据库级别的约束。

以上这些选项只是Django级别的,数据级别依旧是RESTRICT!

1
2
class Article(models.Model):
category = models.ForeignKey('Category', on_delete=models.SET(Category.objects.get(pk=4)), null=True)

表关系笔记:

一对多:

  1. 应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。
  2. 实现方式:一对多或者多对一,都是通过ForeignKey来实现的。还是以文章和作者的案例进行讲解。
1
2
3
4
5
6
7
8
class User(models.Model):
username = models.CharField(max_length=20)
password = models.CharField(max_length=100)

class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
author = models.ForeignKey("User",on_delete=models.CASCADE)

那么以后在给Article对象指定author,就可以使用以下代码来完成:

1
2
3
4
5
6
article = Article(title='abc',content='123')
author = User(username='zhiliao',password='111111')
# 要先保存到数据库中
author.save()
article.author = author
article.save()

并且以后如果想要获取某个用户下所有的文章,可以通过article_set来实现。示例代码如下:

1
2
3
4
5
user = User.objects.first()
# 获取第一个用户写的所有文章
articles = user.article_set.all()
for article in articles:
print(article)

并且如果想要将文章添加到某个分类中。可以使用一下的方式:

1
2
3
4
category = Category.objects.first()
article = Article(title='bbb',content='vvv')
article.author = FrontUser.objects.first()
category.article_set.add(article,bulk=False)
  • 使用bulk=False,那么Django会自动的保存article,而不需要在添加到category之前先保存article。
  • 或者是另外一种解决方式是,在添加到category.article_set中之前,先将article保存到数据库中。但是如果article.category不能为空,那么就产生一种死循环了,article没有category不能保存,而将article添加到cateogry.artile_set中,又需要article之前是已经存储到数据库中的。
  • 如果是上面的那种需求,建议使用bulk=False的解决方案。

一对一:

  1. 在Django中一对一是通过models.OnetToOneField来实现的。这个OneToOneField其实本质上就是一个外键,只不过这个外键有一个唯一约束(unique key),来实现一对一。
  2. 以后如果想要反向引用,那么是通过引用的模型的名字转换为小写的形式进行访问。比如以下模型:
1
2
3
4
5
6
7
8
9
10
class FrontUser(models.Model):
username = models.CharField(max_length=200)

class UserExtension(models.Model):
school = models.CharField(max_length=100)
user = models.OneToOneField("FrontUser",on_delete=models.CASCADE)

# 通过userextension来访问UserExtension对象
user = FrontUser.objects.first()
print(user.userextension)

UserExtension的对象,可以通过user来访问到对应的user对象。并且FrontUser对象可以使用userextension来访问对应的UserExtension对象。
如果不想使用Django默认的引用属性名字。那么可以在OneToOneField中添加一个related_name参数。示例代码如下:

1
2
3
4
5
6
7
8
9
10
class FrontUser(models.Model):
username = models.CharField(max_length=200)

class UserExtension(models.Model):
school = models.CharField(max_length=100)
user = models.OneToOneField("FrontUser",on_delete=models.CASCADE,related_name='extension')

# 通过extension来访问到UserExtension对象
user = FrontUser.objects.first()
print(user.extension)

那么以后就FrontUser的对象就可以通过extension属性来访问到对应的UserExtension对象。

多对多:

  1. 应用场景:比如文章和标签的关系。一篇文章可以有多个标签,一个标签可以被多个文章所引用。因此标签和文章的关系是典型的多对多的关系。

  2. 实现方式:Django为这种多对多的实现提供了专门的Field。叫做ManyToManyField。还是拿文章和标签为例进行讲解。示例代码如下:

1
2
3
4
5
6
7
class Article(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
tags = models.ManyToManyField("Tag",related_name="articles")

class Tag(models.Model):
name = models.CharField(max_length=50)

在数据库层面,实际上Django是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到articletag两张表的主键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def many_to_many_view(request):
# article = Article.objects.first() #获得文章
# tag = Tag(name='冷门文章') #新建标签
# tag.save()
# article.tag_set.add(tag)

# tag = Tag.objects.get(pk=1)
# article = Article.objects.get(pk=3)
# tag.articles.add(article)

article = Article.objects.get(pk=1)
tags = article.tags.all()
for tag in tags:
print(tag)

return HttpResponse("success")

查询条件笔记:

exact

exact:在底层会被翻译成=

iexact

iexact:在底层会被翻译成LIKE

  • LIKE=:大部分情况下都是等价的,只有少数情况下是不等价的。
  • exictiexact:他们的区别其实就是LIKE=的区别,因为exact会被翻译成=,而iexact会被翻译成LIKE
  • 因为field__exact=xxx其实等价于filed=xxx,因此我们直接使用filed=xxx就可以了,并且因为大部分情况exactiexact又是等价的,因此我们以后直接使用field=xxx就可以了。

QuerySet.query

QuerySet.queryquery可以用来查看这个ORM查询语句最终被翻译成的SQL语句。但是query只能被用在QuerySet对象上,不能用在普通的ORM模型上。因此如果你的查询语句是通过get来获取数据的,那么就不能使用query,因为get返回的是满足条件的ORM模型,而不是QuerySet。如果你是通过filter等其他返回QuerySet的方法查询的,那么就可以使用query

contains

contains:使用大小写敏感的判断,某个字符串是否在指定的字段中。这个判断条件会使用大小敏感,因此在被翻译成SQL语句的时候,会使用like binary,而like binary就是使用大小写敏感的。

icontains

icontains:使用大小写不敏感的判断,某个字符串是否被包含在指定的字段中。这个查询语句在被翻译成SQL的时候,使用的是like,而likeMySQL层面就是不区分大小写的。

contains和iexact区别

containsicontains,在被翻译成SQL的时候使用的是%hello%,就是只要整个字符串中出现了hello都能过够被找到,而iexact没有百分号,那么意味着只有完全相等的时候才会被匹配到。

in

in:可以直接指定某个字段的是否在某个集合中。示例代码如下:

1
articles = Article.objects.filter(id__in=[1,2,3])

也可以通过其他的表的字段来判断是否在某个集合中。示例代码如下:

1
categories = Category.objects.filter(article__id__in=[1,2,3])

如果要判断相关联的表的字段,那么也是通过__来连接。并且在做关联查询的时候,不需要写models_set,直接使用模型的名字的小写化就可以了。比如通过分类去查找相应的文章,那么通过article__id__in就可以了,而不是写成article_set__id__in的形式。当然如果你不想使用默认的形式,可以在外键定义的时候传递related_query_name来指定反向查询的名字。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Category(models.Model):
name = models.CharField(max_length=100)

class Meta:
db_table = 'category'

class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
cateogry = models.ForeignKey("Category",on_delete=models.CASCADE,null=True,related_query_name='articles')

class Meta:
db_table = 'article'

因为在cateogryForeignKey中指定了related_query_namearticles,因此你不能再使用article来进行反向查询了。这时候就需要通过articles__id__in来进行反向查询。

反向查询是将模型名字小写化。比如article__in。可以通过related_query_name来指定自己的方式,而不使用默认的方式。
反向引用是将模型名字小写化,然后再加上_set,比如article_set,可以通过related_name来指定自己的方式,而不是用默认的方式。

并且,如果在做反向查询的时候,如果查询的字段就是模型的主键,那么可以省略掉这个字段,直接写成article__in就可以了,不需要这个id了。

in不仅仅可以指定列表/元组,还可以为QuerySet。比如要查询“文章标题中包含有hello的所有分类”,那么可以通过以下代码来实现:

1
2
3
4
articles = Article.objects.filter(title__icontains='hello')
categories = Category.objects.filter(articles__in=articles)
for cateogry in categories:
print(cateogry)

gtgteltlte

gtgteltlte:代表的是大于、大于等于、小于、小于等于的条件。示例代码如下:

1
articles = Article.objects.filter(id__lte=3)  #小于等于3

startswithistartswithendswithiendswith

startswithistartswithendswithiendswith:表示以某个值开始,不区分大小写的以某个值开始、以某个值结束、不区分大小写的以某个值结束。示例代码如下:

1
articles = Article.objects.filter(title__endswith="hello")

时间查询条件

range

range:可以指定一个时间段。并且时间应该标记为aware时间,不然django会报警告。Filtering a DateTimeField with dates won’t include items on the last day. 示例代码如下:

1
2
3
4
5
6
7
8
from datetime import datetime,time
from django.utils.timezone import make_aware

start_time = make_aware(datetime(year=2018,month=4,day=4,hour=17,minute=0,second=0))
end_time = make_aware(datetime(year=2018,month=4,day=4,hour=18,minute=0,second=0))
articles = Article.objects.filter(create_time__range=(start_time,end_time))
print(articles.query)
print(articles)

date

date:用年月日来进行过滤。如果想要使用这个过滤条件,那么前提必须要在MySQL中添加好那些时区文件。示例代码如下:

1
articles = Article.objects.filter(create_time__date=datetime(year=2018,month=4,day=4))

year/month/day

year/month/day:表示根据年/月/日进行查找。示例代码如下:

1
articles = Article.objects.filter(create_time__year__gte=2018)

week_day

week_day:根据星期来进行查找。1表示星期天,7表示星期六,2-6代表的是星期一到星期五。比如要查找星期三的所有文章,那么可以通过以下代码来实现:

1
articles = Article.objects.filter(create_time__week_day=4)

time

time:根据分时秒来进行查找。如果要具体到秒,一般比较难匹配到,可以使用区间的方式来进行查找。区间使用range条件。比如想要获取17时/10分/27-28秒之间的文章,那么可以通过以下代码来实现:

1
2
3
start_time = time(hour=17,minute=10,second=27)
end_time = time(hour=17,minute=10,second=28)
articles = Article.objects.filter(create_time__time__range=(start_time,end_time))

regexiregex

regexiregex:大小写敏感和大小写不敏感的正则表达式。示例代码如下:

1
articles = Article.objects.filter(title__regex=r'^hello')

以上代码的意思是提取所有标题以hello字符串开头的文章。

聚合函数笔记:

  1. 所有的聚合函数都是放在django.db.models下面。
  2. 聚合函数不能够单独的执行,需要放在一些可以执行聚合函数的方法下面中去执行。比如aggregate。示例代码如下:
1
2
3
from django.db.models import Avg

result = Book.objects.aggregate(Avg("price"))
  1. 聚合函数执行完成后,自动给这个聚合函数的值取个名字。取别名的规则,默认是field+__+聚合函数名字形成的。比如以上代码形成的名字叫做price__avg。如果不想使用默认的名字,那么可以在使用聚合函数的时候传递关键字参数进去,参数的名字就是聚合函数执行完成的名字。实示例代码如下:
1
result = Book.objects.aggregate(avg=Avg("price"))

以上传递了关键字参数avg=Avg("price"),那么以后Avg聚合函数执行完成的名字就叫做avg

aggregate

aggregate:这个方法不会返回一个QuerySet对象,而是返回一个字典。这个字典中的key就是聚合函数的名字,值就是聚合函数执行后的结果。

annotate

aggregateannotate的相同和不同:
* 相同:这两个方法都可以执行聚合函数。
* 不同:
- aggregate返回的是一个字典,在这个字典中存储的是这个聚合函数执行的结果。而annotate返回的是一个QuerySet对象,并且会在查找的模型上添加一个聚合函数的属性。
- aggregate不会做分组,而annotate会使用group by子句进行分组,只有调用了group by子句,才能对每一条数据求聚合函数的值。annotate在原来模型字段的基础之上添加一个使用了聚合函数的字段,并且在使用聚合函数的时候,会使用当前这个模型的主键进行分组(group by)。

aggregateannotate方法可以在任何的QuerySet对象上调用。因此只要是返回了QuerySet对象,那么就可以进行链式调用。比如要获取2018年度的销售总额,那么可以先过滤年份,再求聚合函数。示例代码如下:

1
BookOrder.objects.filter(create_time__year=2018).aggregate(total=Sum('price'))

Count

Count:用来求某个数据的个数。比如要求所有图书的数量,那么可以使用以下代码:

1
result = Book.objects.aggregate(book_nums=Count("id"))

并且Count可以传递distinct=True参数,用来剔除那些重复的值,只保留一个。比如要获取作者表中,不同邮箱的个数,那么这时候可以使用distinct=True。示例代码如下:

1
result = Author.objects.aggregate(email_nums=Count('email',distinct=True))

MaxMin

MaxMin:求指定字段的最大值和最小值。示例代码如下:

1
result = Author.objects.aggregate(max=Max("age"),min=Min("age"))

Sum

Sum:求某个字段值的总和。示例代码如下:

1
result = BookOrder.objects.aggregate(total=Sum('price'))

F表达式和Q表达式

F表达式

F表达式: 动态的获取某个字段上的值。并且这个F表达式,不会真正的去数据库中查询数据,他相当于只是起一个标识的作用。比如想要将原来每本图书的价格都在原来的基础之上增加10元,那么可以使用以下代码来实现:

1
2
3
from django.db.models import F

Book.objects.update(price=F("price")+10)
1
2
# 获取名字和邮箱相同的作者
authors = Author.objects.filter(name=F("email"))

Q表达式

Q表达式:使用Q表达式包裹查询条件,可以在条件之间进行多种操作。与、或、非等,从而实现一些复杂的查询操作。例子如下:

  • 查找价格大于100,并且评分达到4.85以上的图书:
1
2
3
4
# 不使用Q表达式的
books = Book.objects.filter(price__gte=100,rating__gte=4.85)
# 使用Q表达式的
books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
  • 查找价格低于100元,或者评分低于4分的图书:
1
books = Book.objects.filter(Q(price__gte=100)&Q(rating__gte=4.85))
  • 获取价格大于100,并且图书名字中不包含”传“字的图书:
1
books = Book.objects.filter(Q(price__gte=100)&~Q(name__icontains='传'))

QuerySet API:

模型.objects:

这个对象是django.db.models.manager.Manager的对象,这个类是一个空壳类,他上面的所有方法都是从QuerySet这个类上面拷贝过来的。因此我们只要学会了QuerySet,这个objects也就知道该如何使用了。

Manager源码解析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class_name = "BaseManagerFromQuerySet"

class_dict = {
'_queryset_class': QuerySet
}

class_dict.update(cls._get_queryset_methods(QuerySet))

# type动态的时候创建类
# 第一个参数是用来指定创建的类的名字。创建的类名是:BaseManagerFromQuerySet
# 第二个参数是用来指定这个类的父类。
# 第三个参数是用来指定这个类的一些属性和方法
return type(class_name,(cls,),class_dict)

_get_queryset_methods:这个方法就是将QuerySet中的一些方法拷贝出来

filter/exclude/annotate:过滤/排除满足条件的/给模型添加新的字段。

1
2
3
Article.objects.exclude(title__contains='hello')

articles = Article.objects.annotate(author_name=F("author__name"))

order_by:

1
2
3
4
5
6
7
8
# 根据创建的时间正序排序
articles = Article.objects.order_by("create_time")
# 根据创建的时间倒序排序
articles = Article.objects.order_by("-create_time")
# 根据作者的名字进行排序
articles = Article.objects.order_by("author__name")
# 首先根据创建的时间进行排序,如果时间相同,则根据作者的名字进行排序
articles = Article.objects.order_by("create_time",'author__name')

一定要注意的一点是,多个order_by,会把前面排序的规则给打乱,而使用后面的排序方式。比如以下代码:

1
articles = Article.objects.order_by("create_time").order_by("author__name")

它会根据作者的名字进行排序,而不是使用文章的创建时间。

当然,也可以在模型定义的在Meta类中定义ordering来指定默认的排序方式。示例代码如下:

1
2
3
class Meta:
db_table = 'book_order'
ordering = ['create_time','-price']

还可以根据annotate定义的字段进行排序。比如要实现图书的销量进行排序,那么示例代码如下:

1
2
3
books = Book.objects.annotate(order_nums=Count("bookorder")).order_by("-order_nums")
for book in books:
print('%s/%s'%(book.name,book.order_nums))

values

只想提取其中的几个字段,可以使用values来进行指定,并且使用了values方法后,提取出的QuerySet中的数据类型不是模型,而是在values方法中指定的字段和值形成的字典:

1
books = Book.objects.values('id','name')

提取这个模型上关联的的对象的属性,查找顺序和filter的用法是一样的:

1
books = Book.objects.values('id','name','author__name')

以上会提取authorname字段,如果想更改字段名,可以使用关键字参数:

1
books = Book.objects.values('id', 'name', author_name=F('author__name'))

自定义的名字,不能和模型上本身拥有的字段一样,否则会报错。

values中,也可以使用聚合函数来形成一个新的字段。比如获取每本图书的销量:

1
books = Book.objects.values('id', 'name', order_nums=Count('bookorder'))

如果调用values方法的时候,没有传递任何的参数,那么会获取这个模型上的所有字段以及对于的值形成的字典:

1
books = Book.objects.values()

那么books中的值如下:

1
{'id':1, 'name':'三国演义', 'pages':987, 'price':108.0, 'rating':3.9, 'author_id':3}

values_list

values是一样的作用,只不过返回的QuerySet中,装的不是字典,而是元组。示例代码如下:

1
books = Book.objects.values_list('id', 'name')

以上的返回结果是:

1
(1,'三国演义')

如果给values_list只指定一个字段,那么我们可以指定flat=True,这样返回的结果就不再是一个元组,而是整个字段的值。flat只能用在只有一个字段的情况下,否则就会报错。示例代码如下:

1
books = Book.objects.values_list('name', flat=True)

那么以上返回的结果是:

1
'三国演义'

all方法

这个简单的返回一个QuerySet对象,这个QuerySet对象没有经过任何的修改(比如:过滤等)。

在查找某个表的数据的时候,可以一次性把相关联的其它表的数据都提取出来。以后再访问相关表的数据的时候,不用再次查找数据库,可以节省一些开销。示例代码如下:

1
2
3
4
books = Book.objects.select_related('author','publisher')
for book in books:
print(book.author.name)
# 因为在提取Book的时候,使用了select_related,那么以后再访问book.author的时候,不会再次向数据库重新发起查询

注意:这个方法只能用在外键的关联对象上,对于那种多对多,或者是多对一的情况,不能使用它来实现,应该使用prefetch_related来实现。

select_related方法类似,也是在查询语句时,提前将指定的数据查找出来。只不过这个方法是用来解决多对多,或者多对一的情况。这个方法会产生两个查询语句。所以,如果在这个方法中查询使用外键关联的模型的时候,也会产生两个查询语句,因此如果查询的是外键关联的模型,建议使用select_related方法。
在查询多对多或者多对一的关联的对象的时候,与使用模型访问传递的字符串一样。比如要获取图书上的所有订单,示例代码如下:

1
books = Book.objects.prefetch_related('bookorder_set')

注意:在使用prefetch_related查找出来的bookorder_set,建议不要再对其进行任何操作,比如filter,不然又会产生N多查询语句。

如果确实想要对预先查找的集合进行操作,可以用django.db.models.Prefetch完成。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
from django.db.models import Prefetch

# 先使用Prefetch吧查找的条件写好,在放到prefetch_related中
prefetch = Prefetch("bookorder_set",queryset=BookOrder.objects.filter(price__gte=90))
books = Book.objects.prefetch_related(prefetch)
for book in books:
print('='*30)
print(book.name)
orders = book.bookorder_set.all()
for order in orders:
print(order.id)

defer和only

这两个方法都会返回一个QuerySet对象,并且这个QuerySet中装的都是模型,而不是字典。

  1. defer:这个方法用来告诉ORM,在查询某个模型的时候,过滤掉某些字段。注意:使用了defer了的字段,如果以后再使用这个字段,会重新发起一次请求。因此要谨慎操作。
  2. only:这个方法用来告诉ORM,在查询某个模型的时候,值提取几个字段。注意:没有加在only中的字段,以后如果使用了,那么也会重新发起一次请求。因此要谨慎操作。
1
2
3
books = Book.objects.only('name')
for book in books:
print('%s/%s'%(book.id,book.price))

get方法

这个方法给定的条件只能匹配到一条数据,如果匹配到多条数据,或没有匹配到任何数据,都会报错。一般用于查询主键。

1
2
3
book = Book.objects.get(id=5)
print(book)
print(connection.queries)

create方法

创建一条数据,并且将这个数据保存到数据库中,以下的代码是等价的:

1
2
3
4
publisher = Publisher(name='人民邮电出版社')
publisher.save()

Publisher.objects.create(name='人民邮电出版社')

get_or_create

如果给定的条件有数据,那么就会把这个数据直接提取出来。如果给定的条件没有数据,那么就会先创建数据,然后再把数据返回回来。这个方法的返回值是一个元组,元组的第一个参数obj是这个对象,第二个参数created代表是否创建的,是返回True

1
obj,created= Category.objects.get_or_create(title='默认分类')

bulk_create

一次性创建多个数据。示例代码如下:

1
2
3
4
publisher = Publisher.objects.bulk_create([
Publisher(name='123出版社'),
Publisher(name='abc出版社'),
])

count

获取提取的数据的个数。如果想要知道总共有多少条数据,那么建议使用count,而不是使用len(articles)这种。因为count在底层是使用select count(*)来实现的,这种方式比使用len函数更加的高效。

firtst和last

返回QuerySet中的第一条和最后一条数据。

aggregate

使用聚合函数。

exists

判断某个条件的数据是否存在。如果要判断某个条件的元素是否存在,那么建议使用exists,这比使用count或者直接判断QuerySet更有效得多。示例代码如下:

1
2
3
4
5
6
7
8
if Article.objects.filter(title__contains='hello').exists():
print(True)
# 比使用count更高效:
if Article.objects.filter(title__contains='hello').count() > 0:
print(True)
# 也比直接判断QuerySet更高效:
if Article.objects.filter(title__contains='hello'):
print(True)

distinct

去除掉那些重复的数据。这个方法如果底层数据库用的是MySQL,那么不能传递任何的参数。比如想要提取所有销售的价格超过80元的图书,并且删掉那些重复的,那么可以使用distinct来帮我们实现,示例代码如下:

1
books = Book.objects.filter(bookorder__price__gte=80).distinct()

需要注意的是,如果在distinct之前使用了order_by,那么因为order_by会提取order_by中指定的字段,因此再使用distinct就会根据多个字段来进行唯一化,所以就不会把那些重复的数据删掉。示例代码如下:

1
orders = BookOrder.objects.order_by("create_time").values("book_id").distinct()

那么以上代码因为使用了order_by,即使使用了distinct,也会把重复的book_id提取出来。

update

执行更新操作,在SQL底层走的也是update命令。比如要将所有category为空的articlearticle字段都更新为默认的分类。示例代码如下:

1
Article.objects.filter(category__isnull=True).update(category_id=3)

注意这个方法走的是更新的逻辑。所以更新完成后保存到数据库中不会执行save方法,因此不会更新auto_now设置的字段。

delete

删除所有满足条件的数据。删除数据的时候,要注意on_delete指定的处理方式。

1
Author.objects.filter(id__gte=3).delete()

切片操作

有时候我们查找数据,有可能只需要其中的一部分。那么这时候可以使用切片操作来帮我们完成。QuerySet使用切片操作就跟列表使用切片操作是一样的。示例代码如下:

1
2
3
books = Book.objects.all()[1:3]   #1,2
for book in books:
print(book)

切片操作并不是把所有数据从数据库中提取出来再做切片操作。而是在数据库层面使用LIMITOFFSET来帮我们完成。所以如果只需要取其中一部分的数据的时候,建议大家使用切片操作。

什么时候Django会将QuerySet转换为SQL去执行

生成一个QuerySet对象并不会马上转换为SQL语句去执行。
比如我们获取Book表下所有的图书:

1
2
books = Book.objects.all()
print(connection.queries)

我们可以看到在打印connection.quries的时候打印的是一个空的列表。说明上面的QuerySet并没有真正的执行。
在以下情况下QuerySet会被转换为SQL语句执行:

  • 迭代:在遍历QuerySet对象的时候,会首先先执行这个SQL语句,然后再把这个结果返回进行迭代。比如以下代码就会转换为SQL语句:
1
2
for book in Book.objects.all():
print(book)
  • 使用步长做切片操作:QuerySet可以类似于列表一样做切片操作。做切片操作本身不会执行SQL语句,但是如果如果在做切片操作的时候提供了步长,那么就会立马执行SQL语句。需要注意的是,做切片后不能再执行filter方法,否则会报错。
  • 调用len函数:调用len函数用来获取QuerySet中总共有多少条数据也会执行SQL语句。
  • 调用list函数:调用list函数用来将一个QuerySet对象转换为list对象也会立马执行SQL语句。
  • 判断:如果对某个QuerySet进行判断,也会立马执行SQL语句。

ORM实例

ORM模型如下:

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
29
30
31
32
33
from django.db import models

class Student(models.Model):
"""学生表"""
name = models.CharField(max_length=100)
gender = models.SmallIntegerField()

class Meta:
db_table = 'student'

class Course(models.Model):
"""课程表"""
name = models.CharField(max_length=100)
teacher = models.ForeignKey("Teacher",on_delete=models.SET_NULL,null=True)

class Meta:
db_table = 'course'

class Score(models.Model):
"""分数表"""
student = models.ForeignKey("Student",on_delete=models.CASCADE)
course = models.ForeignKey("Course",on_delete=models.CASCADE)
number = models.FloatField()

class Meta:
db_table = 'score'

class Teacher(models.Model):
"""老师表"""
name = models.CharField(max_length=100)

class Meta:
db_table = 'teacher'

查询平均成绩大于60分的同学的id和平均成绩;

1
2
3
rows = Student.objects.annotate(avg=Avg("score__number")).filter(avg__gte=60).values("id","avg")
for row in rows:
print(row)

查询所有同学的id、姓名、选课的数、总成绩;

1
2
3
4
rows = Student.objects.annotate(course_nums=Count("score__course"),total_score=Sum("score__number"))
.values("id","name","course_nums","total_score")
for row in rows:
print(row)

查询姓“李”的老师的个数;

1
2
teacher_nums = Teacher.objects.filter(name__startswith="李").count()
print(teacher_nums)

查询没学过“黄老师”课的同学的id、姓名;

1
2
3
rows = Student.objects.exclude(score__course__teacher__name="黄老师").values('id','name')
for row in rows:
print(row)

查询学过课程id为1和2的所有同学的id、姓名;

1
2
3
rows = Student.objects.filter(score__course__in=[1,2]).distinct().values('id','name')
for row in rows:
print(row)

查询学过“黄老师”所教的所有课的同学的学号、姓名;

1
2
3
rows = Student.objects.annotate(nums=Count("score__course",filter=Q(score__course__teacher__name='黄老师'))).filter(nums=Course.objects.filter(teacher__name='黄老师').count()).values('id','name')
for row in rows:
print(row)

查询所有课程成绩小于60分的同学的id和姓名;

1
2
3
students = Student.objects.exclude(score__number__gt=60)
for student in students:
print(student)

查询没有学全所有课的同学的id、姓名;

1
2
3
students = Student.objects.annotate(num=Count(F("score__course"))).filter(num__lt=Course.objects.count()).values('id','name')
for student in students:
print(student)

查询所有学生的姓名、平均分,并且按照平均分从高到低排序;

1
2
3
students = Student.objects.annotate(avg=Avg("score__number")).order_by("-avg").values('name','avg')
for student in students:
print(student)

查询各科成绩的最高和最低分,以如下形式显示:课程ID,课程名称,最高分,最低分:

1
2
3
courses = Course.objects.annotate(min=Min("score__number"),max=Max("score__number")).values("id",'name','min','max')
for course in courses:
print(course)

查询每门课程的平均成绩,按照平均成绩进行排序;

1
2
3
courses = Course.objects.annotate(avg=Avg("score__number")).order_by('avg').values('id','name','avg')
for course in courses:
print(course)

统计总共有多少女生,多少男生;

1
2
rows = Student.objects.aggregate(male_num=Count("gender",filter=Q(gender=1)),female_num=Count("gender",filter=Q(gender=2)))
print(rows)

将“黄老师”的每一门课程都在原来的基础之上加5分;

1
2
rows = Score.objects.filter(course__teacher__name='黄老师').update(number=F("number")+5)
print(rows)

查询两门以上不及格的同学的id、姓名、以及不及格课程数;

1
2
3
students = Student.objects.annotate(bad_count=Count("score__number",filter=Q(score__number__lt=60))).filter(bad_count__gte=2).values('id','name','bad_count')
for student in students:
print(student)

查询每门课的选课人数;

1
2
3
courses = Course.objects.annotate(student_nums=Count("score__student")).values('id','name','student_nums')
for course in courses:
print(course)

ORM模型迁移

命令说明

makemigrations

将模型生成迁移脚本。模型所在的app,必须放在settings.py中的INSTALLED_APPS中。这个命令有以下几个常用选项:

  • app_label:后面可以跟一个或者多个app,那么就只会针对这几个app生成迁移脚本。如果没有任何的app_label,那么会检查INSTALLED_APPS中所有的app下的模型,针对每一个app都生成响应的迁移脚本。
  • --name:给这个迁移脚本指定一个名字。
  • --empty:生成一个空的迁移脚本。如果你想写自己的迁移脚本,可以使用这个命令来实现一个空的文件,然后自己再在文件中写迁移脚本。

migrate

将新生成的迁移脚本。映射到数据库中。创建新的表或者修改表的结构。以下一些常用的选项:

  • app_label:将某个app下的迁移脚本映射到数据库中。如果没有指定,那么会将所有在INSTALLED_APPS中的app下的模型都映射到数据库中。
  • app_label migrationname:将某个app下指定名字的migration文件映射到数据库中。
  • --fake:可以将指定的迁移脚本名字添加到数据库中。但是并不会把迁移脚本转换为SQL语句,修改数据库中的表。
  • --fake-initial:将第一次生成的迁移文件版本号记录在数据库中。但并不会真正的执行迁移脚本。
  • showmigrations:查看某个app下的迁移文件。如果后面没有app,那么将查看INSTALLED_APPS中所有的迁移文件。

sqlmigrate:查看某个迁移文件在映射到数据库中的时候,转换的SQL语句。

migrate原理说明

migrate做了什么事情:

  1. 将相关的迁移脚本翻译成SQL语句,在数据库中执行这个SQL语句。
  2. 如果这个SQL语句执行没有问题,那么就会将这个迁移脚本的名字记录到django_migrations中。

migrate怎么判断哪些迁移脚本需要执行

它会将代码项目中的迁移脚本和数据库中django_migrations表中的迁移脚本进行对比,如果发现数据库中,没有这个迁移脚本,那么就会执行这个迁移脚本。

执行migrate命令的时候报错的解决办法

原因

执行migrate命令会报错的原因是。数据库的django_migrations表中的迁移版本记录和代码中的迁移脚本不一致导致的。

解决办法:

使用--fake参数:

首先对比数据库中的迁移脚本和代码中的迁移脚本。然后找到哪个不同,之后再使用--fake,将代码中的迁移脚本添加到django_migrations中,但是并不会执行sql语句。这样就可以避免每次执行migrate的时候,都执行一些重复的迁移脚本。

终极解决方案:

如果代码中的迁移脚本和数据库中的迁移脚本实在太多,很难找到准确出错位置。那么这时候就可以使用以下终极解决方案。就是清除之前的迁移脚本,重新映射,将出问题的app下的所有模型和数据库中表保持一致。操作步骤如下:

  1. 将出问题的app下的所有模型,都和数据库中的表保持一致。
  2. 将出问题的app下的所有迁移脚本文件都删掉。再在django_migrations表中将出问题的app相关的迁移记录都删掉。
  3. 使用makemigrations,重新将模型生成一个迁移脚本。
  4. 使用migrate --fake-initial参数,将刚刚生成的迁移脚本,标记为已经完成(因为这些模型相对应的表,其实都已经在数据库中存在了,不需要重复执行了)。
  5. 之后可以正常映射了。

根据已有的表自动生成模型

  1. 首先需要在settings.py中配置好数据库相关信息

  2. 通过python manage.py inspectdb > models.py,将表转换为模型后的代码,输出到models.py文件。如果只是想要转换一个表为模型。那么可以指定表的名字python manage.py inspectdb article_article > models.py

  3. 修正模型:新生成的ORM模型有些地方可能不太适合使用。比如模型的名字,表之间的关系等等。那么以下选项还需要重新配置一下:

    • 模型名:自动生成的模型,是根据表的名字生成的,可能不是你想要的。这时候模型的名字你可以改成任何你想要的。
    • 模型所属app:根据自己的需要,将相应的模型放在对应的app中。放在同一个app中也是没有任何问题的。只是不方便管理。
    • 模型外键引用:将所有使用ForeignKey的地方,模型引用都改成字符串。这样不会产生模型顺序的问题。另外,如果引用的模型已经移动到其他的app中了,那么还要加上这个app的前缀。
    • Django管理模型:将Meta下的managed=False删掉,如果保留这个,那么以后这个模型有任何的修改,使用migrate都不会映射到数据库中。
    • 当有多对多的时候,应该也要修正模型。将中间表注释了,然后使用ManyToManyField来实现多对多。并且,使用ManyToManyField生成的中间表的名字可能和数据库中那个中间表的名字不一致,这时候肯定就不能正常连接了。那么可以通过db_table来指定中间表的名字。示例代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Article(models.Model):
    title = models.CharField(max_length=100, blank=True, null=True)
    content = models.TextField(blank=True, null=True)
    author = models.ForeignKey('front.User', models.SET_NULL, blank=True, null=True)
    # 使用ManyToManyField模型到表,生成的中间表的规则是:article_tags
    # 但现在已经存在的表的名字叫做:article_tag
    # 可以使用db_table,指定中间表的名字
    tags = models.ManyToManyField("Tag",db_table='article_tag')

    class Meta:
    db_table = 'article'
    • 表名:切记不要修改表的名字。不然映射到数据库中,会发生找不到对应表的错误。
  4. 执行命令python manage.py makemigrations生成初始化的迁移脚本。方便后面通过ORM来管理表。这时候还需要执行命令python manage.py migrate [app_name] --fake-initial,因为如果不使用--fake-initial,那么会将迁移脚本会映射到数据库中。这时候迁移脚本会新创建表,而这个表之前是已经存在了的,所以肯定会报错。此时我们只要将这个0001-initial的状态修改为已经映射,而不真正执行映射,下次再migrate的时候,就会忽略它。

  5. Django的核心表映射到数据库中:Django中还有一些核心的表也是需要创建的。不然有些功能是用不了的。比如auth相关表。如果这个数据库之前就是使用Django开发的,那么这些表就已经存在了。可以不用管了。如果之前这个数据库不是使用Django开发的,那么应该使用migrate命令将Django中的核心模型映射到数据库中。

请作者喝冰阔落