从零开始玩转python、DJango框架中ROM数据库模型

ORM 概念
对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。 简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。 ORM在业务逻辑层和数据库层之间充当了桥梁的作用。

ORM 与 DB 的对应关系
ORM 面向对象和关系型数据库的一种映射,通过操作对象的方式操作数据库数据,不支持对库的操作,只能操作表 对应关系: 类 --> 表 对象 --> 数据行 属性 --> 字段
Model 模块 在Django中model是你数据的单一、明确的信息来源。它包含了你存储的数据的重要字段和行为。通常, 一个模型(model)映射到一个数据库表。

基本情况:

每个模型都是一个Python类,它是django.db.models.Model的子类。
模型的每个属性都代表一个数据库字段。
综上所述,Django为您提供了一个自动生成的数据库访问API,详询官方文档

一、数据库环境搭建
1、mysql中新建一个数据库

sql语法:

CREATE DATABASE [IF NOT EXISTS] <数据库名>
[[DEFAULT] CHARACTER SET <字符集名>] 
[[DEFAULT] COLLATE <校对规则名>];

或者推荐用Navicat直接创建,视图化,简单粗暴。
2、安装mysqlclient
打开cmd命令行

pip install mysqlclient

pass:有的同学安装可能会失败,请参考我的另一篇文章:mysqlclient安装失败如何解决

3、打开django的seeting设置

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',  # 数据库主机
        'PORT': 3306,  # 数据库端口
        'USER': 'root',  # 数据库用户名
        'PASSWORD': 'root',  # 数据库用户密码
        'NAME': 'test'  # 数据库名字
    }
}

从零开始玩转python、DJango框架中ROM数据库模型_第1张图片4、检查是否配置成功

pycharm terminal中执行

python manage.py dbshell

可进入mysql虚拟中断,表示配置成功
从零开始玩转python、DJango框架中ROM数据库模型_第2张图片

二、ORM表模型

1、在jango中,所有的模型必须继承

from django.db.models import Model

这个类,字段类型需要使用models模块中定义好的字段类型。

#案例说明
from django.db import models

# Create your models here.

class PhoneField(models.Field):  # 自定义的char类型的字段类

    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super(PhoneField, self).__init__(max_length=max_length, *args, **kwargs)

    def db_type(self, connection):  # 限定生成数据库表的字段类型为char,长度为max_length指定的值的字符串
        return 'char(%s)' % self.max_length

class Student(models.Model):
    s_name = models.CharField(max_length=64,help_text="学生姓名")
    s_sex = models.IntegerField(choices=((0,'男'),(1,'女')), help_text="性别")
    s_phone = PhoneField(max_length=11,help_text="手机号")
    # auto_now_add=True 在每一次数据被添加进去的时候,记录当前时间
    create_time = models.DateTimeField(auto_now_add=True)
    # auto_now=True 在每一次数据被保存的时候,记录当前时间
    update_time = models.DateTimeField(auto_now=True)

    class Meta:
        db_table='student' # 指定表名

2、模型中常用属性

字段 描述 示例
AutoField int自增列,必须填入参数 primary_key=True id=models.AutoField(primary_key=True)
BigAutoField bigint自增列,必须填入参数 primary_key=True id=models.BigAutoField(AutoField)
BinaryField 二进制类型
BooleanField 数据库存为True–> 1 False–> 0
CharField 字符类型,必须提供max_length参数。max_length表示字符的长度 name = models.CharField(max_length=32)
DateField date
DateTimeField 日期时间 birth = models.DateTimeField(auto_now_add=True)
DecimalField 十进制的小数 money= models.DecimalField(max_digits=15,decimal_places=3)
DurationField 长整数,时间间隔,数据库中按照bigint存储,ORM中获取的值为datetime.timedelta类型
FloatField 浮点型
IntegerField 一个整数类型。数值的范围是 -2147483648 ~ 2147483647。 age = models.IntegerField()
BigIntegerField 长整型(有符号的) -9223372036854775808 ~ 9223372036854775807
IPAddressField char(15)
NullBooleanField 可以为空的布尔值
OneToOneField integer
PositiveIntegerField 正整数 0 ~ 2147483647
PositiveSmallIntegerField 正小整数 0 ~ 32767
SmallAutoField smallint AUTO_INCREMENT
SmallIntegerField 小整数 -32768 ~ 32767
TextField 比较长的字符串
TimeField 时间格式 HH:MM[:ss[.uuuuuu]]

3、django提供的一些特殊属性

EmailField(CharField):
- 字符串类型,Django Admin以及ModelForm中提供验证机制
- e_mail = models.EmailField(max_length=255, unique=True)

IPAddressField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供验证 IPV4 机制

GenericIPAddressField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供验证 Ipv4和Ipv6
    - 参数:
        protocol,用于指定Ipv4或Ipv6, 'both',"ipv4","ipv6"
        unpack_ipv4, 如果指定为True,则输入::ffff:192.0.2.1时候,可解析为192.0.2.1,开启此功能,需要protocol="both"

URLField(CharField)
    - 字符串类型,Django Admin以及ModelForm中提供验证 URL

SlugField(CharField)
    - 字符串类型,Django Admin以及ModelForm中提供验证支持 字母、数字、下划线、连接符(减号)

CommaSeparatedIntegerField(CharField)
    - 字符串类型,格式必须为逗号分割的数字

UUIDField(Field)
    - 字符串类型,Django Admin以及ModelForm中提供对UUID格式的验证

FilePathField(Field)
    - 字符串,Django Admin以及ModelForm中提供读取文件夹下文件的功能
    - 参数:
            path,                      文件夹路径
            match=None,                正则匹配
            recursive=False,           递归下面的文件夹
            allow_files=True,          允许文件
            allow_folders=False,       允许文件夹

FileField(Field)
    - 字符串,路径保存在数据库,文件上传到指定目录
    - 参数:
        upload_to = ""      上传文件的保存路径
        storage = None      存储组件,默认django.core.files.storage.FileSystemStorage

ImageField(FileField)
    - 字符串,路径保存在数据库,文件上传到指定目录
    - 参数:
        upload_to = ""      上传文件的保存路径
        storage = None      存储组件,默认django.core.files.storage.FileSystemStorage
        width_field=None,   上传图片的高度保存的数据库字段名(字符串)
        height_field=None   上传图片的宽度保存的数据库字段名(字符串)

4、自定义字段

示例:我们发现上边没有固定长度的字符串类型,但是手机号字段一般都是定长11位的字符串,所以我们需要自定义一些字段类型

from django.db import models

class PhoneField(models.Field):  # 自定义的char类型的字段类

    def __init__(self, max_length, *args, **kwargs):
        self.max_length = max_length
        super(PhoneField, self).__init__(max_length=max_length, *args, **kwargs)

    def db_type(self, connection):  # 限定生成数据库表的字段类型为char,长度为max_length指定的值的字符串
        return 'char(%s)' % self.max_length

5、Field的常用参数:

参数 含义
null 数据库中字段是否可以为空#注意 数据库的null 和’'空字符是两种情况
db_column 数据库中字段的列名
default 数据库中字段的默认值
primary_key 数据库中字段是否为主键
db_index 数据库中字段是否可以建立索引
unique 数据库中字段是否可以建立唯一索引
unique_for_date 数据库中字段【日期】部分是否可以建立唯一索引
unique_for_month 数据库中字段【月】部分是否可以建立唯一索引
unique_for_year 数据库中字段【年】部分是否可以建立唯一索引
verbose_name Admin中显示的字段名称
blank Admin中是否允许用户输入为空
editable Admin中是否可以编辑
help_text Admin中该字段的提示信息
choices Admin中显示选择框的内容,用不变动的数据放在内存中从而避免跨表操作如:gf = models.IntegerField(choices=[(0, ‘何穗’),(1, ‘大表姐’),],default=1)
error_messages 自定义错误信息(字典类型),从而定制想要显示的错误信息;字典健:null, blank, invalid, invalid_choice, unique, and unique_for_date 如:{‘null’: “不能为空.”, ‘invalid’: ‘格式错误’}
validators 自定义错误验证(列表类型),从而定制想要的验证规则

6、模型中Meta配置:

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

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 # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
verbose_name admin中显示的表名称
verbose_name_plural 所有用户信息
index_together 联合索引
unique_together 联合唯一索引

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

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

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模型。这样使用起来非常的方便。示例代码如下:

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对象呢。因此在底层,Django为Article表添加了一个属性名_id的字段(比如author的字段名称是author_id),这个字段是一个外键,记录着对应的作者的主键。以后通过article.author访问的时候,实际上是先通过author_id找到对应的数据,然后再提取User表中的这条数据,形成一个模型。

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

# 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)

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

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来指定。可以指定的类型如下:

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

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

表之间的关系都是通过外键来进行关联的。而表之间的关系,无非就是三种关系:一对一、一对多(多对一)、多对多等。以下将讨论一下三种关系的应用场景及其实现方式。
一对一:

应用场景:比如一个用户表和一个用户信息表。在实际网站中,可能需要保存用户的许多信息,但是有些信息是不经常用的。如果把所有信息都存放到一张表中可能会影响查询效率,因此可以把用户的一些不常用的信息存放到另外一张表中我们叫做UserExtension。但是用户表User和用户信息表UserExtension就是典型的一对一了。

实现方式:Django为一对一提供了一个专门的Field叫做OneToOneField来实现一对一操作。示例代码如下:

 class User(models.Model):
     username = models.CharField(max_length=20)
     password = models.CharField(max_length=100)

 class UserExtension(models.Model):  
     birthday = models.DateTimeField(null=True)  
     school = models.CharField(blank=True,max_length=50)  
     user = models.OneToOneField("User", on_delete=models.CASCADE)

在UserExtension模型上增加了一个一对一的关系映射。其实底层是在UserExtension这个表上增加了一个user_id,来和user表进行关联,并且这个外键数据在表中必须是唯一的,来保证一对一。

一对多:

应用场景:比如文章和作者之间的关系。一个文章只能由一个作者编写,但是一个作者可以写多篇文章。文章和作者之间的关系就是典型的多对一的关系。

实现方式:一对多或者多对一,都是通过ForeignKey来实现的。还是以文章和作者的案例进行讲解。

 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,就可以使用以下代码来完成:

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

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

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

多对多:

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

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

 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是为这种多对多的关系建立了一个中间表。这个中间表分别定义了两个外键,引用到article和tag两张表的主键。

related_name和related_query_name:
related_name:

还是以User和Article为例来进行说明。如果一个article想要访问对应的作者,那么可以通过author来进行访问。但是如果有一个user对象,想要通过这个user对象获取所有的文章,该如何做呢?这时候可以通过user.article_set来访问,这个名字的规律是模型名字小写_set。示例代码如下:

user = User.objects.get(name='张三')
user.article_set.all()

如果不想使用模型名字小写_set的方式,想要使用其他的名字,那么可以在定义模型的时候指定related_name。示例代码如下:

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    # 传递related_name参数,以后在方向引用的时候使用articles进行访问
    author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles')

以后在方向引用的时候。使用articles可以访问到这个作者的文章模型。示例代码如下:

user = User.objects.get(name='张三')
user.articles.all()

如果不想使用反向引用,那么可以指定related_name=’+’。示例代码如下:

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    # 传递related_name参数,以后在方向引用的时候使用articles进行访问
    author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='+')

以后将不能通过user.article_set来访问文章模型了。
related_query_name:

在查找数据的时候,可以使用filter进行过滤。使用filter过滤的时候,不仅仅可以指定本模型上的某个属性要满足什么条件,还可以指定相关联的模型满足什么属性。比如现在想要获取写过标题为abc的所有用户,那么可以这样写:

users = User.objects.filter(article__title='abc')

如果你设置了related_name为articles,因为反转的过滤器的名字将使用related_name的名字,那么上例代码将改成如下:

users = User.objects.filter(articles__title='abc')

可以通过related_query_name将查询的反转名字修改成其他的名字。比如article。示例代码如下:

class Article(models.Model):
    title = models.CharField(max_length=100)
    content = models.TextField()
    # 传递related_name参数,以后在方向引用的时候使用articles进行访问
    author = models.ForeignKey("User",on_delete=models.SET_NULL,null=True,related_name='articles',related_query_name='article')

那么在做反向过滤查找的时候就可以使用以下代码:

users = User.objects.filter(article__title='abc')

三、ORM模型迁移

1、生成迁移脚本

python manage.py makemigrations

2、执行迁移标本,数据库中创建新表

python manage.py migrate
  • 迁移命令 详解:

    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语句。

  • migrations中的迁移版本和数据库中的迁移版本对不上怎么办?

    找到哪里不一致,然后使用python manage.py --fake [版本名字],将这个版本标记为已经映射。
    删除指定app下migrations和数据库表django_migrations中和这个app相关的版本号,然后将模型中的字段和数据库中的字段保持一致,再使用命令python manage.py makemigrations重新生成一个初始化的迁移脚本,之后再使用命令python manage.py makemigrations --fake-initial来将这个初始化的迁移脚本标记为已经映射。以后再修改就没有问题了。

更多关于迁移脚本的。请查看官方文档:https://docs.djangoproject.com/en/2.0/topics/migrations/

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

在实际开发中,有些时候可能数据库已经存在了。如果我们用Django来开发一个网站,读取的是之前已经存在的数据库中的数据。那么该如何将模型与数据库中的表映射呢?根据旧的数据库生成对应的ORM模型,需要以下几个步骤:

Django给我们提供了一个inspectdb的命令,可以非常方便的将已经存在的表,自动的生成模型。想要使用inspectdb自动将表生成模型。首先需要在settings.py中配置好数据库相关信息。不然就找不到数据库。示例代码如下:
 DATABASES = {
     'default': {
         'ENGINE': 'django.db.backends.mysql',
         'NAME': "migrations_demo",
         'HOST': '127.0.0.1',
         'PORT': '3306',
         'USER': 'root',
         'PASSWORD': 'root'
     }
 }
比如有以下表:
article表:
tag表:
article_tag表:
front_user表:

那么通过

python manage.py inspectdb

,就会将表转换为模型后的代码,显示在终端:

from django.db import models

class ArticleArticle(models.Model):
  title = models.CharField(max_length=100)
  content = models.TextField(blank=True, null=True)
  create_time = models.DateTimeField(blank=True, null=True)
  author = models.ForeignKey('FrontUserFrontuser', models.DO_NOTHING, blank=True, null=True)

  class Meta:
      managed = False
      db_table = 'article_article'

class ArticleArticleTags(models.Model):
  article = models.ForeignKey(ArticleArticle, models.DO_NOTHING)
  tag = models.ForeignKey('ArticleTag', models.DO_NOTHING)

  class Meta:
      managed = False
      db_table = 'article_article_tags'
      unique_together = (('article', 'tag'),)

class ArticleTag(models.Model):
  name = models.CharField(max_length=100)

  class Meta:
      managed = False
      db_table = 'article_tag'

class FrontUserFrontuser(models.Model):
  username = models.CharField(max_length=100)
  telephone = models.CharField(max_length=11)

  class Meta:
      managed = False
      db_table = 'front_user_frontuser'
    以上代码只是显示在终端。如果想要保存到文件中。那么可以使用>重定向输出到指定的文件。比如让他输出到models.py文件中。示例命令如下:
python manage.py inspectdb > models.py
    以上的命令,只能在终端执行,不能在pycharm->Tools->Run manage.py Task...中使用。

    如果只是想要转换一个表为模型。那么可以指定表的名字。示例命令如下:
python manage.py inspectdb article_article > models.py
修正模型:新生成的ORM模型有些地方可能不太适合使用。比如模型的名字,表之间的关系等等。那么以下选项还需要重新配置一下:

    模型名:自动生成的模型,是根据表的名字生成的,可能不是你想要的。这时候模型的名字你可以改成任何你想要的。

    模型所属app:根据自己的需要,将相应的模型放在对应的app中。放在同一个app中也是没有任何问题的。只是不方便管理。

    模型外键引用:将所有使用ForeignKey的地方,模型引用都改成字符串。这样不会产生模型顺序的问题。另外,如果引用的模型已经移动到其他的app中了,那么还要加上这个app的前缀。

    让Django管理模型:将Meta下的managed=False删掉,如果保留这个,那么以后这个模型有任何的修改,使用migrate都不会映射到数据库中。

    当有多对多的时候,应该也要修正模型。将中间表注视了,然后使用ManyToManyField来实现多对多。并且,使用ManyToManyField生成的中间表的名字可能和数据库中那个中间表的名字不一致,这时候肯定就不能正常连接了。那么可以通过db_table来指定中间表的名字。示例代码如下:
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'

表名:切记不要修改表的名字。不然映射到数据库中,会发生找不到对应表的错误。

   执行命令
   python manage.py makemigrations
   生成初始化的迁移脚本。方便后面通过ORM来管理表。这时候还需要执行命令
   python manage.py    migrate   

–fake-initial,因为如果不使用–fake-initial,那么会将迁移脚本会映射到数据库中。这时候迁移脚本会新创建表,而这个表之前是已经存在了的,所以肯定会报错。此时我们只要将这个0001-initial的状态修改为已经映射,而不真正执行映射,下次再migrate的时候,就会忽略他。

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

abstract 创建公共模型父类

在创建模型的时候,其实有好多字段都是模型中共同存在的,例如createtime,updatetime,mark等等,这些字段我们需要在每个模型中重复创建,这就造成了工作量的浪费,我们可以通过创建一个公共的模型父类来,子类集成该父类,这样我们就不需要重复写这些字段了

class PublicModel(models.Model):
createtime = models.DateTimeField(auto_now_add=True,verbose_name=“创建日期”)
update = models.DateTimeField(auto_now=True,verbose_name = ‘修改日期’)
status = models.IntegerField(verbose_name = ‘状态’)
class Meta:
abstract=True # 父类中必须指定该属性,否则该类会被创建到数据库

四、子类继承PublicModel类,就不需要重复写createtime,update,status三个字段的定义了

class Student(PublicModel):
    s_name = models.CharField('学生姓名', max_length=64, null=False, help_text='学生姓名')
    s_sex = models.CharField("性别", max_length=1, choices=(
        (0, '男'),
        (1, '女')
    ), help_text='性别:0-男;1-女')
    s_age = models.PositiveIntegerField('年龄', null=False, default=0, help_text='年龄')
    s_dept = models.CharField("专业", max_length=64, null=False, help_text="专业")
    si_id = models.OneToOneField(to='StudentInfo', to_field="id", on_delete=models.CASCADE, db_column='si_id',
                                 related_name="stu_stu_info")
    course = models.ManyToManyField(to="Course", db_table="stu_course", related_name="course_student")

    class Meta:
        db_table = 'student'

七、ORM操作

模型的操作:

在ORM框架中,所有模型相关的操作,比如添加/删除等。其实都是映射到数据库中一条数据的操作。因此模型操作也就是数据库表中数据的操作。
添加一个模型到数据库中:

添加模型到数据库中。首先需要创建一个模型。创建模型的方式很简单,就跟创建普通的Python对象是一摸一样的。在创建完模型之后,需要调用模型的save方法,这样Django会自动的将这个模型转换成sql语句,然后存储到数据库中。示例代码如下:

1、进入django的虚拟终端
打开setting,修改

USE_TZ = False

2、常用操作

# 获取总数,对应SQL:select count(1) from User

User.objects.count()

# 获取总数,对应SQL:
select count(1) from User where name = '运维咖啡吧’

User.objects.filter(name='运维咖啡吧').count()

# 大于,>,对应SQL:select * from User where id > 724

User.objects.filter(id__gt=724)

# 大于等于,>=,对应SQL:select * from User where id >= 724

User.objects.filter(id__gte=724)

# 小于,<,对应SQL:select * from User where id < 724
User.objects.filter(id__lt=724)

# 小于等于,<=,对应SQL:select * from User where id <= 724

User.objects.filter(id__lte=724)

# 同时大于和小于, 1 < id < 10,对应SQL:select * from User where id > 1 and id < 10

User.objects.filter(id__gt=1, id__lt=10)

# 包含,in,对应SQL:select * from User where id in (11,22,33)

User.objects.filter(id__in=[11, 22, 33])

# 不包含,not in,对应SQL:select * from User where id not in (11,22,33)

User.objects.exclude(id__in=[11, 22, 33])

# 为空:isnull=True,对应SQL:select * from User where pub_date is null

User.objects.filter(pub_date__isnull=True)

# 不为空:isnull=False,对应SQL:select * from User where pub_date is not null

User.objects.filter(pub_date__isnull=True)

# 匹配,like,大小写敏感,对应SQL:select * from User where name like ‘%sre%’,SQL中大小写不敏感

User.objects.filter(name__contains="sre")

# 匹配,like,大小写不敏感,对应SQL:select * from User where name like ‘%sre%’,SQL中大小写不敏感

User.objects.filter(name__icontains="sre")

# 不匹配,大小写敏感,对应SQL:select * from User where name not like ‘%sre%’,SQL中大小写不敏感

User.objects.exclude(name__contains="sre")

# 不匹配,大小写不敏感,对应SQL:select * from User where name not like ‘%sre%’,SQL中大小写不敏感

User.objects.exclude(name__icontains="sre")

# 范围,between and,对应SQL:select * from User where id between 3 and 8

User.objects.filter(id__range=[3, 8])

# 以什么开头,大小写敏感,对应SQL:select * from User where name like ‘sh%’,SQL中大小写不敏感

User.objects.filter(name__startswith='sre')

# 以什么开头,大小写不敏感,对应SQL:select * from User where name like ‘sh%’,SQL中大小写不敏感

User.objects.filter(name__istartswith='sre')

# 以什么结尾,大小写敏感,对应SQL:select * from User where name like ‘%sre’,SQL中大小写不敏感

User.objects.filter(name__endswith='sre')

# 以什么结尾,大小写不敏感,对应SQL:select * from User where name like ‘%sre’,SQL中大小写不敏感

User.objects.filter(name__iendswith='sre')

# 排序,order by,正序,对应SQL:select * from User where name = ‘运维咖啡吧’ order by id

User.objects.filter(name='运维咖啡吧').order_by('id')

# 多级排序,order by,先按name进行正序排列,如果name一致则再按照id倒叙排列

User.objects.filter(name='运维咖啡吧').order_by('name','-id')

# 排序,order by,倒序,对应SQL:select * from User where name = ‘运维咖啡吧’ order by id desc

User.objects.filter(name='运维咖啡吧').order_by('-id')

进阶操作

# limit,对应SQL:select * from User limit 3;

User.objects.all()[:3]

# limit,取第三条以后的数据,没有对应的SQL,类似的如:select * from User limit 3,10000000,从第3条开始取数据,取10000000条(10000000大于表中数据条数)

User.objects.all()[3:]

# offset,取出结果的第10-20条数据(不包含10,包含20),也没有对应SQL,参考上边的SQL写法

User.objects.all()[10:20]

# 分组,group by,对应SQL:select username,count(1) from User group by username;

from django.db.models import Count
User.objects.values_list('username').annotate(Count('id'))
分组查询

annotate 是使用查询结果集中的每一项生成统计数据

from django.db.models import Avg, Sum, Max, Min, Count
#第一个values确定分组字段,annotate分组操作 第二个values确定展示字段
user = User.objects.all().values("sex").annotate(count=Count('id')).values("sex",'count')
print(student)

having如何实现:在queryset后面跟filter(count = 1).values(‘name’,‘sex’)

User.objects.all().values("sex").annotate(count=Count('id')).filter(count = 1).values("sex",'count')

# 去重distinct,对应SQL:select distinct(username) from User

User.objects.values('username').distinct().count()

# filter多列、查询多列,对应SQL:select username,fullname from accounts_user

User.objects.values_list('username', 'fullname')

# filter单列、查询单列,正常values_list给出的结果是个列表,里边里边的每条数据对应一个元组,当只查询一列时,可以使用flat标签去掉元组,将每条数据的结果以字符串的形式存储在列表中,从而避免解析元组的麻烦

User.objects.values_list('username', flat=True)

# int字段取最大值、最小值、综合、平均数

from django.db.models import Sum,Count,Max,Min,Avg

User.objects.aggregate(Count(id))
User.objects.aggregate(Sum(‘age’))

时间字段

# 匹配日期,date

User.objects.filter(create_time__date=datetime.date(2018, 8, 1))
User.objects.filter(create_time__date__gt=datetime.date(2018, 8, 2))

# 匹配年,year

User.objects.filter(create_time__year=2018)
User.objects.filter(create_time__year__gte=2018)

# 匹配月,month

User.objects.filter(create_time__month__gt=7)
User.objects.filter(create_time__month__gte=7)

# 匹配日,day

User.objects.filter(create_time__day=8)
User.objects.filter(create_time__day__gte=8)

# 匹配周,week_day

 User.objects.filter(create_time__week_day=2)
User.objects.filter(create_time__week_day__gte=2)

# 匹配时,hour

User.objects.filter(create_time__hour=9)
User.objects.filter(create_time__hour__gte=9)

# 匹配分,minute

User.objects.filter(create_time__minute=15)
User.objects.filter(create_time__minute_gt=15)

# 匹配秒,second

User.objects.filter(create_time__second=15)
User.objects.filter(create_time__second__gte=15)

按天统计归档

today = datetime.date.today()
select = {'day': connection.ops.date_trunc_sql('day', 'create_time')}
deploy_date_count = Task.objects.filter(
    create_time__range=(today - datetime.timedelta(days=7), today)
).extra(select=select).values('day').annotate(number=Count('id'))

Q 的使用

Q对象可以对关键字参数进行封装,从而更好的应用多个查询,可以组合&(and)、|(or)、~(not)操作符。

例如下边的语句

from django.db.models import Q

User.objects.filter(
    Q(role__startswith='sre_'),
    Q(name='公众号') | Q(name='运维咖啡吧')
)

转换成SQL语句如下:

select * from User where role like ‘sre_%’ and (name=‘公众号’ or name=‘运维咖啡吧’)

通常更多的时候我们用Q来做搜索逻辑,比如前台搜索框输入一个字符,后台去数据库中检索标题或内容中是否包含

_s = request.GET.get('search')

_t = Blog.objects.all()
if _s:
    _t = _t.filter(
        Q(title__icontains=_s) |
        Q(content__icontains=_s)
    )

return _t

如何分页

from django.core.paginator import Paginator
p = Paginator(User.objects.all(),per_page=2)
#get_page()表示获取第几页的数据,返回一个Page对象,object_list获取对象中的数据列表
print(p.get_page(1).object_list)
#获取第二页的name和phone的字段
print(p.get_page(2).object_list.values('name','phone'))
方法 作用
count 所有页面对象总数,即统计object_list中item数目。
num_pages 页面总数。
pagiator.page_range 页面范围,从1开始,例如[1,2,3,4]
page(1) 获取第一页
Page.has_next () 如果有下一页,则返回 True
Page.has_previous () 如果有上一页,返回 True
Page.has_other_pages () 如果有上一页或下一页,返回 True
Page.next_page_number () 返回下一页的页码。如果下一页不存在,抛出 InvlidPage 异常
Page.previous_page_number () 返回上一页的页码。如果上一页不存在,抛出InvalidPage异常
Page.start_index () 返回当前页上的第一个对象,相对于分页列表的所有对象的序号,从1开始。比如,将五个对象的列表分为每页两个对象,第二页的 start_index() 会返回 3
Page.end_index () 返回当前页上的最后一个对象,相对于分页列表的所有对象的序号,从1开始。 比如,将五个对象的列表分为每页两个对象,第二页的 end_index() 会返回 4
Page.object_list 当前页上所有对象的列表
Page.number 当前页的序号,从1开始
Page.paginator 相关的 Paginator 对象

外键:ForeignKey

表结构:

class Role(models.Model):
    name = models.CharField(max_length=16, unique=True)


class User(models.Model):
    username = models.EmailField(max_length=255, unique=True)
    role = models.ForeignKey(Role, on_delete=models.CASCADE)
正向查询:

查询用户的角色名

_t = User.objects.get(username='运维咖啡吧')
_t.role.name
反向查询:

查询角色下包含的所有用户

_t = Role.objects.get(name='Role03')
_t.user_set.all()

    另一种反向查询的方法:

_t = Role.objects.get(name='Role03')

这种方法比上一种_set的方法查询速度要快

User.objects.filter(role=_t)
第三种反向查询的方法:
如果外键字段有related_name属性,例如models如下:

class User(models.Model):
    username = models.EmailField(max_length=255, unique=True)
    role = models.ForeignKey(Role, on_delete=models.CASCADE,related_name='roleUsers')

那么可以直接用related_name属性取到某角色的所有用户

_t = Role.objects.get(name = 'Role03')
_t.roleUsers.all()

  M2M:ManyToManyField

表结构:

class Group(models.Model):
    name = models.CharField(max_length=16, unique=True)

class User(models.Model):
    username = models.CharField(max_length=255, unique=True)
    groups = models.ManyToManyField(Group, related_name='groupUsers')
正向查询:

查询用户隶属组

_t = User.objects.get(username = '运维咖啡吧')
_t.groups.all()
反向查询:

查询组包含用户

_t = Group.objects.get(name = 'groupC')
_t.user_set.all()

同样M2M字段如果有related_name属性,那么可以直接用下边的方式反查

_t = Group.objects.get(name = 'groupC')
_t.groupUsers.all()
get_object_o

  get_or_create

顾名思义,查找一个对象如果不存在则创建,如下:

object, created = User.objects.get_or_create(username='运维咖啡吧')

返回一个由object和created组成的元组,其中object就是一个查询到的或者是被创建的对象,created是一个表示是否创建了新对象的布尔值

实现方式类似于下边这样:

try:
    object = User.objects.get(username='运维咖啡吧')

    created = False
exception User.DoesNoExist:
    object = User(username='运维咖啡吧')
    object.save()

    created = True

returen object, created

执行原生SQL

Django中能用ORM的就用它ORM吧,不建议执行原生SQL,可能会有一些安全问题,如果实在是SQL太复杂ORM实现不了,那就看看下边执行原生SQL的方法,跟直接使用pymysql基本一致了

from django.db import connection

with connection.cursor() as cursor:
    cursor.execute('select * from accounts_User')
    row = cursor.fetchall()

return row

如何修改某个值

修改单条数据

在查找到数据后,便可以进行修改了。修改的方式非常简单,只需要将查找出来的对象的某个属性进行修改,然后再调用这个对象的save方法便可以进行修改。示例代码如下:

# 查询出需要修改的数据,注意get和filter查询出来的数据结构不一样

stu = Student.objects.get(id=1)
stu.s_sex=0
stu.save()

批量修改数据

使用filter或者exclude批量查询出数据,然后使用update进行批量修改

# 使用filter或者exclude批量查询出数据,然后使用update进行批量修改

stus = Student.objects.filter(id__in=[1,2])
stus.update(s_sex=1)

删除数据:

在查找到数据后,便可以进行删除了。删除数据非常简单,只需要调用这个对象的delete方法即可。实例代码如下:

stu = Student.objects.get(id='6')
stu.delete()

什么是逻辑删除,什么是物理删除

物理删除 逻辑删除
将数据从数据库删除 将数据标记删除
删除掉了就不会占磁盘了 实质上只是修改某个字段的值,还是会占用磁盘空间的
删除掉了就找不回来了 删除后还可恢复
删除后通过条件查询不展现给用户看

事务与回滚

什么是事务和回滚

事务:多个数据逻辑操作的集合 回滚:多个逻辑中某个操作出错,回到初始状态
事务的原子性要求事务要么全部完成,要么全部不完成,不可能停滞在某个中间状态 什么情况下需要使用事务

  • 修改多个ORM模型做时需要使用事务和回滚操作 对结果要求严格一致(要么全部成功,要么全部失败)
  • django中怎么使用事务自动进行事务提交和回滚
from django.db import transaction

# 使用@transaction.atomic实现自动进行事务提交和回滚
@transaction.atomic
def test(request):
    # 事务提交
    Student.objects.filter(id=1).update(s_sex=1) # 若没有事务,这次操作是会成功的
    stu = Student.objects.get(id=0) # 查询不到id=0的学生,get方法会报错
    stu.s_sex=1
    stu.save()
    Student.objects.filter(id=2).update(s_sex=1)# 失败
    return HttpResponse("ok")
    
def test(request):
    # 使用上下文管理器实现自动进行事务提交和回滚
    with transaction.atomic():
        Student.objects.get(id=1).update(s_sex=1) # 若没有事务,这次操作是会成功的
        stu = Student.objects.get(id=0) # 查询不到id=0的学生,get方法会报错
        stu.s_sex=1
        stu.save()
        Student.objects.filter(id=2).update(s_sex=1)# 失败
        return HttpResponse("ok")

**

以上两种方法二选其一即可

**

手动进行事务提交和回滚

因为django设置是自动提交的,所以在实现手动事务提交和回滚操作之前,要关闭django事务自动提交

def test(request):
    # 事务提交
    try:
        transaction.set_autocommit(False)# 关闭django自动提交
        Student.objects.get(id=1).update(s_sex=1) # 若没有事务,这次操作是会成功的
        stu = Student.objects.get(id=0) # 查询不到id=0的学生,get方法会报错
        stu.s_sex=1
        stu.save()
        Student.objects.filter(id=2).update(s_sex=1)# 失败
        transaction.commit() # 操作成功,事务提交
    except:
        print("操作失败,事务回滚")
        transaction.rollback()  # 操作失败,事务回滚
        return HttpResponse("ok")

你可能感兴趣的:(Django,mysql,python,数据库,django)