天天看点

Python进阶篇(二)-- Django 深入模型

        上一节提到了Django是基于MVC架构的Web框架,MVC架构追求的是“模型”和“视图”的解耦合。所谓“模型”说得更直白一些就是数据(的表示),所以通常也被称作“数据模型”。在实际的项目中,数据模型通常通过数据库实现持久化操作,而关系型数据库在过去和当下都是持久化的首选方案,下面我们通过完成一个投票项目来讲解和模型相关的知识点。投票项目的首页会展示某在线教育平台所有的学科;点击学科可以查看到该学科的老师及其信息;用户登录后在查看老师的页面为老师投票,可以投赞成票和反对票;未登录的用户可以通过登录页进行登录;尚未注册的用户可以通过注册页输入个人信息进行注册。在这个项目中,我们使用MySQL数据库来实现数据持久化操作。

1 ORM模型

1.1 ORM介绍

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。

        简单的说,ORM 是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。ORM 在业务逻辑层和数据库层之间充当了桥梁的作用。ORM 解决的主要问题是对象和关系的映射。它通常把一个类和一个表一一对应,类的每个实例对应表中的一条记录,类的每个属性对应表中的每个字段,具体如下图所示。ORM 提供了对数据库的映射,不用直接编写 SQL 代码,只需像操作对象一样从数据库操作数据。让软件开发人员专注于业务逻辑的处理,提高了开发效率。

Python进阶篇(二)-- Django 深入模型

        ORM 模式也是有一定缺点的,它会在一定程度上牺牲程序的执行效率。此外,还存在许多复杂场景是 ORM 模式无法解决的,同样还是需要手动编写 SQL 语句完成。

1.2 创建 Django 项目

        首先创建Django项目vote,在项目下创建名为polls的应用和保存模板页的文件夹tempaltes,项目文件夹的结构如下所示。

Python进阶篇(二)-- Django 深入模型
Python进阶篇(二)-- Django 深入模型

        根据上面描述的项目需求,这里准备了四个静态页面,分别是展示学科的页面

subjects.html

,显示学科老师的页面

teachers.html

,登录页面

login.html

,注册页面

register.html

,稍后我们会将静态页修改为Django项目所需的模板页。

1.3 数据库中生成模型表

        在 Django 中,一个模型(model)会映射到一个数据库表。每个模型都是一个 Python 类,它是

django.db.models.Model

的子类,模型的每个属性都代表一个数据库字段。

(1) 在 polls 中添加数据模型

        在 polls 的

models.py

中添加如下代码:

from django.db import models        # 引入Django.db.models模块


class Subject(models.Model):
    """
    编写Subject模型类,数据模型应该继承于models.Model或其子类
    """
    no = models.AutoField(primary_key=True, verbose_name='编号')
    name = models.CharField(max_length=50, verbose_name='名称')
    intro = models.CharField(max_length=1000, verbose_name='介绍')
    is_hot = models.BooleanField(verbose_name='是否热门')

    def __str__(self):
        return self.name

    class Meta:
        # 通过db_table自定义数据表名
        db_table = 'tb_subject'


class Teacher(models.Model):
    """
    编写Teacher模型类,数据模型应该继承于models.Model或其子类
    """
    sex_choices = (
        (0, '女'),
        (1, '男'),
    )
    no = models.AutoField(primary_key=True, verbose_name='编号')
    name = models.CharField(max_length=20, verbose_name='姓名')
    sex = models.BooleanField(default=True, verbose_name='性别', choices=sex_choices)
    birth = models.DateField(verbose_name='出生日期')
    intro = models.CharField(max_length=1000, verbose_name='个人介绍')
    photo = models.ImageField(max_length=255, verbose_name='照片')
    gcount = models.IntegerField(default=0, db_column='gcount', verbose_name='好评数')
    bcount = models.IntegerField(default=0, db_column='bcount', verbose_name='差评数')
    sno = models.ForeignKey(Subject, on_delete=models.CASCADE, db_column='sno')

    def __str__(self):
        return self.name

    class Meta:
        db_table = 'tb_teacher'
           

        Subject 和 Teacher 模型中的每一个属性都指明了models下面的一个数据类型,代表了数据库中的一个字段。上面的类在数据库中会创建如下的表,见1.3节的第二步创建表格的过程。

(2) 迁移模型

        使用 Django 给我们提供的两个命令来在数据库中生成 polls 应用下定义的数据模型,第一步生成迁移文件,第二步将迁移文件应用到数据库:

python manage.py makemigrations
python manage.py migrate
           

1.3 自动生成数据模型

1. 配置关系型数据库MySQL

(1) 在MySQL中创建数据库,创建用户,授权用户访问该数据库。

create database vote default charset utf8;
create user 'username'@'%' identified by 'yourpassword';
grant all privileges on vote.* to 'username'@'%';
flush privileges;
           

(2) 在 MySQL 中创建保存学科和老师信息的二维表(保存用户信息的表稍后处理)。

use vote;

-- 创建学科表
create table `tb_subject`
(
	`no` integer auto_increment comment '学科编号',
    `name` varchar(50) not null comment '学科名称',
    `intro` varchar(1000) not null default '' comment '学科介绍',
    `is_hot` boolean not null default 0 comment '是不是热门学科',
    primary key (`no`)
);
-- 创建老师表
create table `tb_teacher`
(
    `no` integer auto_increment comment '老师编号',
    `name` varchar(20) not null comment '老师姓名',
    `sex` boolean not null default 1 comment '老师性别',
    `birth` date not null comment '出生日期',
    `intro` varchar(1000) not null default '' comment '老师介绍',
    `photo` varchar(255) not null default '' comment '老师照片',
    `gcount` integer not null default 0 comment '好评数',
    `bcount` integer not null default 0 comment '差评数',
    `sno` integer not null comment '所属学科',
    primary key (`no`),
    foreign key (`sno`) references `tb_subject` (`no`)
);
           

(3) 安装数据库的驱动,Python 3.x 使用 pymysql 作为 MySQL的驱动,然后在Django项目文件夹的

__init__.py

中添加如下所示的代码:

import pymysql
pymysql.install_as_MySQLdb()        # 为了pymysql发挥最大数据库操作性能
           

         温馨提示: 如果使用Django 2.2及以上版本,还会遇到PyMySQL跟Django框架的兼容性问题,兼容性问题会导致项目无法运行,需要按照GitHub上PyMySQL仓库Issues中提供的方法进行处理。总体来说,使用pymysql会比较麻烦,强烈建议大家首选安装mysqlclient,mysqlclient执行效率也比较高。

(4) 修改项目的settings.py文件,首先将我们创建的应用polls添加已安装的项目(INSTALLED_APPS)中,然后配置MySQL作为持久化方案。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'polls',     # 把应用文件加入
]

ATABASES = {
    'default': {
        # 数据库引擎配置
        'ENGINE': 'django.db.backends.mysql',
        # 数据库的名字
        'NAME': 'vote',
        # 数据库服务器的IP地址(本机可以写为localhost或127.0.0.1)
        'HOST': 'localhost',
        # 启动MySQL服务的端口号
        'PORT': 3306,
        # 数据库用户名和口令
        'USER': 'carpediem',
        'PASSWORD': 'carpediem2021',
        # 数据库使用的字符集
        'CHARSET': 'utf8',
        # 数据库时间日期的时区设定
        'TIME_ZONE': 'Asia/Chongqing',
    }
}
           

        在配置ENGINE属性时,常用的可选值包括:

  • 'django.db.backends.sqlite3'

    :SQLite嵌入式数据库。
  • 'django.db.backends.postgresql'

    :BSD许可证下发行的开源关系型数据库产品。
  • 'django.db.backends.mysql'

    :甲骨文公司经济高效的数据库产品。
  • 'django.db.backends.oracle'

    :甲骨文公司关系型数据库旗舰产品。

        其他的配置可以参考官方文档中的数据库配置部分。

(5) Django框架提供了ORM来解决数据持久化问题,ORM翻译成中文叫“对象关系映射”。因为Python是面向对象的编程语言,我们在Python程序中使用对象模型来保存数据,而关系型数据库使用关系模型,用二维表来保存数据,这两种模型并不匹配。使用ORM是为了实现对象模型到关系模型的双向转换,这样就不用在Python代码中书写SQL语句和游标操作,因为这些都会由ORM自动完成。利用Django的ORM,我们可以直接将刚才创建的学科表和老师表变成Django中的模型类。

python manage.py inspectdb > polls/models.py
           

我们可以对自动生成的模型类稍作调整,代码如下所示。

from django.db import models


class Subject(models.Model):
    no = models.AutoField(primary_key=True, verbose_name='编号')
    name = models.CharField(max_length=50, verbose_name='名称')
    intro = models.CharField(max_length=1000, verbose_name='介绍')
    is_hot = models.BooleanField(verbose_name='是否热门')

    class Meta:
        managed = False
        db_table = 'tb_subject'


class Teacher(models.Model):
    no = models.AutoField(primary_key=True, verbose_name='编号')
    name = models.CharField(max_length=20, verbose_name='姓名')
    sex = models.BooleanField(default=True, verbose_name='性别')
    birth = models.DateField(verbose_name='出生日期')
    intro = models.CharField(max_length=1000, verbose_name='个人介绍')
    photo = models.ImageField(max_length=255, verbose_name='照片')
    gcount = models.IntegerField(default=0, db_column='gcount', verbose_name='好评数')
    bcount = models.IntegerField(default=0, db_column='bcount', verbose_name='差评数')
    sno = models.ForeignKey(Subject, models.DO_NOTHING, db_column='sno')

    class Meta:
        managed = False
        db_table = 'tb_teacher'
           

        若你的确想要允许 Django 管理这些表格的生命周期,你需要将上面的 managed 选项的值改为 True (或者删掉它,因为 True 是默认值)

         温馨提示: 所有模型都是

django.db.models.Model

类的子类,模型类跟关系型数据库的二维表对应,模型对象跟表中的记录对应,模型对象的属性跟表中的字段对应。每个字段由

django.db.models.Field

子类(内置在Django core)的实例表示,它们并将被转换为数据库的列。

        该功能仅是一个快捷方式,不是最佳的创建模型的方法。参考 inspectdb 文档 获取更多信息

补充:

1. 通用字段属性

选项 说明

null

数据库中对应的字段是否允许为

NULL

,默认为

False

blank

后台模型管理验证数据时,是否允许为

NULL

,默认为

False

choices

设定字段的选项,各元组中的第一个值是设置在模型上的值,第二值是人类可读的值

db_column

字段对应到数据库表中的列名,未指定时直接使用字段的名称

db_index

设置为

True

时将在该字段创建索引

db_tablespace

为有索引的字段设置使用的表空间,默认为

DEFAULT_INDEX_TABLESPACE

default

字段的默认值

editable

字段在后台模型管理或

ModelForm

中是否显示,默认为

True

error_messages

设定字段抛出异常时的默认消息的字典,其中的键包括

null

blank

invalid

invalid_choice

unique

unique_for_date

help_text

表单小组件旁边显示的额外的帮助文本。

primary_key

将字段指定为模型的主键,未指定时会自动添加

AutoField

用于主键,只读。

unique

设置为

True

时,表中字段的值必须是唯一的

verbose_name

字段在后台模型管理显示的名称,未指定时使用字段的名称

2.

ForeignKey

属性

  1. limit_choices_to

    :值是一个Q对象或返回一个Q对象,用于限制后台显示哪些对象。
  2. related_name

    :用于获取关联对象的关联管理器对象(反向查询),如果不允许反向,该属性应该被设置为

    '+'

    ,或者以

    '+'

    结尾。
  3. to_field

    :指定关联的字段,默认关联对象的主键字段。
  4. db_constraint

    :是否为外键创建约束,默认值为

    True

  5. on_delete

    :外键关联的对象被删除时对应的动作,可取的值包括

    django.db.models

    中定义的:
    • CASCADE

      :级联删除。
    • PROTECT

      :抛出

      ProtectedError

      异常,阻止删除引用的对象。
    • SET_NULL

      :把外键设置为

      null

      ,当

      null

      属性被设置为

      True

      时才能这么做。
    • SET_DEFAULT

      :把外键设置为默认值,提供了默认值才能这么做。

3.

ManyToManyField

属性

  1. symmetrical

    :是否建立对称的多对多关系。
  2. through

    :指定维持多对多关系的中间表的Django模型。
  3. throughfields

    :定义了中间模型时可以指定建立多对多关系的字段。
  4. db_table

    :指定维持多对多关系的中间表的表名。

4. 模型元数据选项

选项 说明

abstract

设置为True时模型是抽象父类

app_label

如果定义模型的应用不在INSTALLED_APPS中可以用该属性指定

db_table

模型使用的数据表名称

db_tablespace

模型使用的数据表空间

default_related_name

关联对象回指这个模型时默认使用的名称,默认为<model_name>_set

get_latest_by

模型中可排序字段的名称。

managed

设置为True时,Django在迁移中创建数据表并在执行flush管理命令时把表移除

order_with_respect_to

标记对象为可排序的

ordering

对象的默认排序

permissions

创建对象时写入权限表的额外权限

default_permissions

默认为

('add', 'change', 'delete')

unique_together

设定组合在一起时必须独一无二的字段名

index_together

设定一起建立索引的多个字段名

verbose_name

为对象设定人类可读的名称

verbose_name_plural

设定对象的复数名称

2 使用ORM完成模型的CRUD操作

2.1 基本的增删改查

        有了Django框架的ORM,我们可以直接使用面向对象的方式来实现对数据的CRUD(增删改查)操作。我们可以在PyCharm的终端中输入下面的命令进入到Django项目的交互式环境,然后尝试对模型的操作。

python manage.py shell
           

1. 新增

from polls.models import Subject

Subject.objects.create(name='H5前端开发', intro='前段比较热的学科', is_hot=True)

subject1 = Subject(name='Python全栈开发', intro='当下最热门的学科', is_hot=True)
subject1.save()
subject2 = Subject(name='全栈软件测试', intro='学习自动化测试的学科', is_hot=False)
subject2.save()
subject3 = Subject(name='JavaEE分布式开发', intro='基于Java语言的服务器应用开发', is_hot=True)
           

2. 删除

subject = Subject.objects.get(no=2)
subject.delete()
           

3. 更新

subject = Subject.objects.get(no=1)
subject.name = 'Python全栈+人工智能'
subject.save()
           

4. 查询

(1) 查询所有对象

(2) 过滤数据

# 查询名称为“Python全栈+人工智能”的学科
Subject.objects.filter(name='Python全栈+人工智能')

# 查询名称包含“全栈”的学科(模糊查询)
Subject.objects.filter(name__contains='全栈')
Subject.objects.filter(name__startswith='全栈')
Subject.objects.filter(name__endswith='全栈')

# 查询所有热门学科
Subject.objects.filter(is_hot=True)

# 查询编号大于3小于10的学科
Subject.objects.filter(no__gt=3).filter(no__lt=10)
Subject.objects.filter(no__gt=3, no__lt=10)

# 查询编号在3到7之间的学科
Subject.objects.filter(no__gte=3, no__lte=7)
Subject.objects.filter(no__range=(3, 7))
           

(3) 查询单个对象

# 查询主键为1的学科
Subject.objects.get(pk=1)
Subject.objects.get(no=1)
Subject.objects.filter(no=1).first()
Subject.objects.filter(no=1).last()
           

(4) 排序

# 查询所有学科按编号升序排列
Subject.objects.order_by('no')
# 查询所有部门按部门编号降序排列
Subject.objects.order_by('-no')
           

(5) 切片

# 按编号从小到大查询前3个学科
Subject.objects.order_by('no')[:3]
           

(6) 计数

# 查询一共有多少个学科
Subject.objects.count()
           

(7) 高级查询

# 查询编号为1的学科的老师
Teacher.objects.filter(sno__no=1)
Subject.objects.get(pk=1).teacher_set.all() 

# 查询学科名称有“全栈”二字的学科的老师
Teacher.objects.filter(sno__name__contains='全栈') 
           

        上面的 objects 是一个特殊的属性,通过它来查询数据库,它是模型的一个 Manager。在 filter() 方法中还有一些比较神奇的双下划线辅助我们进一步过滤结果,详细了解,请阅读官方文档————执行查询

温馨提示:

  • 说明1:由于老师与学科之间存在多对一外键关联,所以能通过学科反向查询到该学科的老师(从一对多关系中“一”的一方查询“多”的一方),反向查询属性默认的名字是类名小写_set(如上面例子中的teacher_set),当然也可以在创建模型时通过ForeingKey的related_name属性指定反向查询属性的名字。如果不希望执行反向查询可以将related_name属性设置为’+‘或者以’+'开头的字符串。
  • 说明2:ORM查询多个对象时会返回QuerySet对象,QuerySet使用了惰性查询,即在创建QuerySet对象的过程中不涉及任何数据库活动,等真正用到对象时(对QuerySet求值)才向数据库发送SQL语句并获取对应的结果,这一点在实际开发中需要引起注意!
  • 说明3:如果希望更新多条数据,不用先逐一获取模型对象再修改对象属性,可以直接使用QuerySet对象的

    update()

    方法一次性更新多条数据。

参考

  • Django 使用原生的 SQL 语句操作 MySQL 数据库:https://www.imooc.com/wiki/djangolesson/nativesql.html
  • 深入模型:https://gitee.com/zengyujin/Python-100-Days/blob/master/Day41-55/42.深入模型.md
  • Django入门指南-第5章:模型设计:https://www.bookstack.cn/read/django-beginners-guide-zh/Fundamentals-2.md
  • Django v4.0 中文文档 模型:https://www.bookstack.cn/read/Django-4.0-zh/94d959954f3d0daa.md