Python学习笔记——Day11

Python学习笔记——Day11

Django第二天。

ORM

ORM(Object-Relation Mapping)意思是对象-关系映射,是通过使用描述对象和数据库之间映射的元数据,将面向对象程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式。这也同时暗示着额外的执行开销;然而,如果ORM作为一种中间件实现,则会有很多机会做优化,而这些在手写的持久层并不存在。更重要的是用于控制转换的元数据需要提供和管理;但是同样,这些花费要比维护手写的方案要少;而且就算遵守ODMG规范的对象数据库依然需要类级别的元数据。

ORM定义

对象关系映射是随着面向对象的软件开发方法发展而产生的。面向对象的开发方法是当今企业级应用开发环境中的主流开发方法,关系数据库是企业级应用环境中永久存放数据的主流数据存储系统。对象和关系数据是业务实体的两种表现形式,业务实体在内存中表现为对象,在数据库中表现为关系数据。内存中的对象之间存在关联和继承关系,而在数据库中,关系数据无法直接表达多对多关联和继承关系。因此,对象-关系映射ORM系统一般以中间件的形式存在,主要实现程序对象到关系数据库数据的映射。面向对象是从软件工程基本原则(如耦合、聚合、封装)的基础上发展起来的,而关系数据库则是从数学理论发展而来的,两套理论存在显著的区别。为了解决这个不匹配的现象,对象关系映射技术应运而生。O/R中字母O起源于“对象”(Object),而R则来自于“关系”(Relational)。几乎所有的程序里面,都存在对象和关系数据库。在业务逻辑层和用户界面层中,我们是面向对象的。当对象信息发生变化的时候,我们需要把对象的信息保存在关系数据库中。目前流行的ORM产品如Java的Hibernate,.Net的EntityFormerWork等。

ORM的好处

在MVC框架中的Model模块中都包括ORM,对于开发人员主要带来了如下好处:

  • 实现了数据模型与数据库的解耦,通过简单的配置就可以轻松更换数据库,而不需要修改代码。
  • 只需要面向对象编程,不需要面向数据库编写代码。
  • 在MVC中Model中定义的类,通过ORM与关系型数据库中的表对应,对象的属性体现对象间的关系,这种关系也被映射到数据表中。

创建一个demo

创建虚拟环境:

python -m venv env

执行env\Scripts目录下的activate,进入虚拟环境

E:\python-projects\django_second_day\env\Scripts\activate

在虚拟环境下,安装django,安装mysqlclient

python -m pip install django
pip install mysqlclient

创建Django项目

django-admin startproject django_second_day

打开django_second_day目录下的settings.py文件,找到DATABASES项,默认使用的是SQLites3数据库

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

修改数据库为MySQL数据库

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': 'django_second_day', #数据库名字,
        'USER': 'root', #数据库登录用户名
        'PASSWORD': '123456', #数据库登录密码
        'HOST': 'localhost', #数据库所在主机
        'PORT': '3306', #数据库端口
    }
}

注意这里数据库需要我们自己手动创建好,我们回到项目根目录,创建一个应用bookapp

python manage.py startapp bookapp

然后我们将bookapp注册到项目中,在settings.py文件中,添加到INSTALLED_APPS中

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

定义模型类

应用中的模型类都定义在应用文件夹下的models.py文件中,比如我们定义的模型类就应该在“bookapp/models.py”文件中。需要记住的是模型类必须继承自django.db.models中的Model类。我们定义两个类,一个图书类,一个人物类代表书中的人物。

from django.db import models

# Create your models here.

# 图书类Book
class Book(models.Model):
    name = models.CharField(max_length=32)
    pub_date = models.DateField()
    read_num = models.IntegerField(default=0)
    isDelete = models.BooleanField(default=False)


# 人物类
class Human(models.Model):
    name = models.CharField(max_length=32)
    gender = models.BooleanField(default=True)
    # 人物简介
    comment = models.CharField(max_length=512)
    # 图书,人物和图书的关系定义为图书1:n人物
    book = models.ForeignKey('Book', on_delete=models.CASCADE)
    isDelete = models.BooleanField(default=False)

然后生成迁移文件,执行迁移

python manage.py makemigrations
python manage.py  migrate

这样我们的数据表就已经建立了,表名分别是bookapp_book和bookapp_human。

表中添加几条数据。

INSERT INTO bookapp_book(NAME,pub_date,read_num,isDelete) VALUES
('射雕英雄传','1981-1-1',12,0),
('天龙八部','1976-7-23',36,0),
('倚天屠龙记','1998-11-2',20,0),
('鹿鼎记','1955-12-12',58,0);

INSERT INTO bookapp_human(NAME,gender,book_id,COMMENT,isDelete) VALUES
('郭靖',1,1,'降龙十八掌',0),
('黄蓉',0,1,'打狗棍法',0),
('黄药师',1,1,'弹指神通',0),
('欧阳锋',1,1,'蛤蟆功',0),
('乔峰',1,2,'降龙十八掌',0),
('段誉',1,2,'六脉神剑',0),
('虚竹',1,2,'天山六阳掌',0),
('王语嫣',0,2,'神仙姐姐',0),
('张无忌',1,3,'九阳神功',0),
('赵敏',0,3,'郡主',0),
('谢逊',1,3,'七伤拳',0),
('张三丰',1,3,'太极拳',0),
('康熙',1,4,'钱',0),
('韦小宝',1,4,'抓奶龙抓手',0);

定义视图,打开bookapp/views.py并进行编辑。

from django.shortcuts import render, redirect
from bookapp.models import *
from datetime import date

# Create your views here.

# 首页查询所有图书并返回
def index(request):
    book_list = Book.objects.all()
    return render(request, 'bookapp/index.html', {'list': book_list})

# 创建新图书
def save(request):
    book = Book()
    book.name = '龙珠'
    book.pub_date = date(1986,12,2)
    book.save()
    return redirect('/bookapp')

# 逻辑删除图书
def delete(request, id):
    book = Book.objects.get(id=int(id))
    book.delete()
    return redirect('/bookapp')

在根目录创建templates/bookapp文件夹,再在bookapp里面创建index.html。

<!DOCTYPE html>
<html lang="utf-8">
    <head>
        <title>书</title>
    </head>
    <body>
        <a href="/bookapp/save/">新增</a>
        <ul>
            {% for book in list %}
            <li>书名:{{book.name}}  <a href="/bookapp/delete{{book.id}}/">删除</a></li>
            {% endfor %}
        </ul>
    </body>
</html>

在bookapp下创建urls.py,配置url如下。

from django.urls import path, re_path
from . import views

urlpatterns = [
	path('', views.index, name='index'),
    path('save/', views.save, name='save'),
    re_path(r'^delete(\d+)/$',views.delete,name='delete'),
]

修改django_second_day目录下的urls.py。

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('bookapp/', include('bookapp.urls')),
]

控制台输入python manage.py runserver运行服务器。浏览器中访问localhost:8000/bookapp,这样一个小demo就完成了。

定义属性

Django会为我们创建的模型类创建自动增长的默认主键列id,每个模型类只能有一个主键列,如果使用选项设置某属性作为这张表的主键,那么Django就不会创建默认主键。

属性命名限制:

  • 不能使用Python的保留关键字
  • 不允许使用连续的下划线,这是由Django的查询方式所决定
  • 定义属性时需要指定字段类型,通过字段类型的参数指定选项,例如:
属性 = models.字段类型(选项)

字段类型

字段类型 含义
AutoField 自动增长的IntegerField,也就是自动增长的整数,通常不需要指定,不指定时Django会自动创建属性名为id的自动增长属性
booleanField 布尔类型字段,True或False
NullBooleanField 三种值,Null、True、False
CharField(max_length=字符长度) 字符串,参数是值最大长度
TextField 大文本字段
IntegerField 整数
DecimalField(max_digits=None, decimal_places=None) 十进制浮点数,第一个参数指总位数,第二个指小数位数
FloatField 浮点数
DateField[auto_now=False, auto_now_add=False])) 日期,auto_now表示每次保存对象时,自动保存为当前时间,用于更新的时间戳,总是指向最后一次修改时间,也就是当前时间,默认为false,auto_now_add表示当对象创建后保存创建的时间,也就是第一次创建时的时间,用于创建的时间戳,总是指向当前时间,默认为false。注意两个参数不可同为true
TimeField 时间,参数和DateField相同
DateTimeField 日期时间,参数和DateField相同
FileField 文件,上传文件字段
ImageField 继承于FileField,对上传的内容进行校验,确保是有效的图片

选项

选项 含义
null 为True表示允许为空,默认值为False
blank 为True表示字段允许为空,默认值是False
以上两者区别 null是数据库范畴的概念,blank是表单验证范畴的概念
db_column 字段的名称,未指定的话使用属性名称
db_index 为True表示在表中为此字段创建索引,默认值为False
default 默认值
primary_key 为True表示该字段为主键,默认为False,一般作AutoField的选项用
unique 为True表示这个字段必须唯一,默认值为False

在bookapp/models.py中对两个类进行修改,代码如下

from django.db import models

# Create your models here.

# 图书类Book
class Book(models.Model):
    # 添加db_column选项,修改数据库字段名为title
    name = models.CharField(max_length=32, db_column="title")
    pub_date = models.DateField()
    read_num = models.IntegerField(default=0)
    isDelete = models.BooleanField(default=False)


# 人物类
class Human(models.Model):
    name = models.CharField(max_length=32)
    # 设置数据库字段为sex
    gender = models.BooleanField(default=True, db_column="sex")
    # 人物简介,null数据库字段允许为空,blank是指输入框不能为空
    comment = models.CharField(max_length=512, null=True, blank=False)
    # 图书,人物和图书的关系定义为图书1:n人物
    book = models.ForeignKey('Book', on_delete=models.CASCADE)
    isDelete = models.BooleanField(default=False)

生成迁移文件,然后执行迁移。查看数据库的字段内容改变。

字段查询

接下来在settings.py文件中添加如下配置,用以在控制台打印sql语句

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'level': 'DEBUG',
            'class': 'logging.StreamHandler',
        },
    },

    'loggers': {
        'django.db.backends': {
            'handlers': ['console'],
            'propagate': True,
            'level': 'DEBUG',
        },
    }
}

在Django中可以通过调用一些方法来完成条件查询,比如filter()、exclude() 、get()等方法,参数的语法格式如下

属性名称__比较运算符=值

说明:属性名称和比较运算符间有两个下划线,所以属性名中不能包含连续的下划线。

条件运算符

等于使用exact,比如查询编号为1的图书

book = Book.objects.filter(id__exact=1)
简化为
book = Book.objects.fiter(id=1)

模糊查询使用contains,包含

book = Book.objects.filter(title__contains='雕')

如果包含%,无需转义,直接使用。

以…开头或结尾 startswith、endswith:以指定的值开头或结尾

book = Book.objects.filter(title__startswith='射')

以上运算符都区分大小写,在这些运算符前加上 i 表示不区分大小写,如iexact、icontains、istartswith、iendswith。
空查询 isnull:是否为null。

book = Book.objects.filter(title__isnull=False)

范围查询in:是否包含在范围内。

book = Book.objects.filter(in__in=[1,2,3])

比较查询 gt、gte、lt、lte:大于、大于等于、小于、小于等于。
查询 id 大于等于 2 的图书

book = Book.objects.filter(id__gte=2)

不等于使用 exclude() 过滤器。
查询 id 不等于 1 的图书

book = Book.objects.exclude(id=1)

日期查询 year、month、day、week_day、hour、minute、second:对日期时间类型的属性进行运算。
查询 1981 年发表的图书。

book = Book.objects.filter(pub_date__year=1981)

查询 1985 年 5 月 1 日后发表的作品

book = Book.objects.filter(pub_date__gt=date(1985, 5, 1))

注意:filter() 方法返回的是查询集,结果内容是 [] 列表内的值,而 get() 方法返回的是单个结果。

F对象

之前的查询都是对象的属性与常量值比较,使用 django.db.models.F 对象比较两个属性。语法如下:

F(属性名)

比如查询 id 的值小于阅读量的图书

book = Book.objects.filter(id__lt=F('read_num'))

Q对象

多个过滤器逐个调用表示逻辑与关系,相当于 and。
查询阅读量大于 30,并且名字中包含“龙”的图书

book = Book.objects.filter(read_num__gt=30, title__contains='龙')
或
book = Book.objects.filter(read_num__gt=30).filter(title__contains='龙')

逻辑或 or 的查询需要 django.db.models.Q() 对象结合 | 运算符实现。语法:

Q(属性名__运算符=值)

查询名字中包含“龙”或“英”的图书

book = Book.objects.filter(Q(title__contains="龙") | Q(title__contains="英"))

事实上逻辑与还可以使用 Q() 对象实现,需要配合使用 & 运算符。
查询阅读量大于 30,并且名字中包含“龙”的图书

book = Book.objects.filter(Q(read_num__gt=30) & Q(title__contains='龙'))

非,使用 ~ 操作符。
查询编号不等于 1 的图书

book = Book.objects.filter(~Q(id=1))

聚合函数

使用 aggregate() 过滤器调用聚合函数,Avg,Count,Max,Min,Sum,均定义于 django.db.models 中。
查询所有图书书目

book = Book.objects.aggregate(Count('id'))

这时,aggregate 的返回值是一个字典类型,格式如下:

{'属性名__聚合类小写': 值}
比如上述
{'id__count': 4}

事实上,使用 count 一般不使用 aggregate() 过滤器,而是下面的方式,返回一个数字

book_num = Book.objects.count()

查询集

刚刚也有提到,filter() 方法返回值是一个查询集,那么什么是查询集呢?查询集表示从数据库中获取的对象集合,一些过滤器方法会返回查询集,查询集可以含义零个、一个或多个过滤器。过滤器基于所给的参数限制查询的结果,从 Sql 的角度来看,查询集就相当于 select 语句的结果,过滤器为限制条件,类似于 where 和 limit 子句。

返回查询集的过滤器:

  • all():返回全部数据
  • filter():返回满足条件的数据
  • exclude():返回满足条件之外的数据,相当于 sql 语句中 where 部分 not 关键字
  • order_by() :对结果进行排序

返回单个值的过滤器:

  • get():返回单个满足条件的对象
    • 如果未找到会引发“模型类.DoesNotExist”异常
    • 如果多条被返回,会引发“模型类.MultipleObjects.Returned”异常
  • count():返回当前查询结果的总数。
  • aggregate():聚合,返回一个字典。

判断一个查询集是否有数据:exist() 方法,判断查询集中是否有数据,如果有则返回True,否则返回False。

两大特性

  1. 惰性执行:创建查询集不会访问数据库,直到调用数据时,才会访问数据库,调用数据的方式有迭代、序列化、与 if 一起使用。
  2. 缓存:使用同一个查询集,第一次使用时会到数据库查询,接着把结果缓存,第二次调用时会使用缓存的数据。

限制查询集

可以对查询集进行取下标或切片操作,等同于 sql 中的 limit 和 offset 子句。不支持负数索引。对查询集进行切片后返回一个新的查询集,不会立即执行查询。如果获取一个对象,直接使用 [0],等同于 [0:1].get(),但是如果没有数据,[0] 引发 IndexError 异常,[0:1].get() 如果没有数据引发 DoesNotExist 异常。

模型类的关系

关系型数据库中的关系,包含有三种,一对多,一对一,多对多,在 Django 中对应的字段类型如下:

  • ForeignKey:一对多,该字段应该定义在多的一端。
  • ManytoManyField:多对多,将字段定义在任意一端。
  • OneToOneField:一对一,将字段定义在任意一端。

关联查询

Django 实现关联查询有两种方式,一种是通过对象执行关联查询,一种是通过模型类执行关联查询。

通过对象执行关联查询

在定义模型类时,可以指定三种关联关系,最常用的时一对多关系,本次所写的“图书-人”为一对多关系。由一访问到多的访问语法:

1 的一方模型类.多对应的一方模型类名小写_set
也就是:
book = Book.objects.get(id=1)
book.human_set.all()

反过来由多的一方访问一的一方的语法:

直接使用多对应的模型类对象.该模型类中关系类的属性名称
human = Human.objects.get(id=1)
human.book

访问一对应的模型类关联对象的 id 语法:

多对应的模型类对象.关联类属性_id
human = Human.objects.get(id=1)
human.book_id

通过模型类执行关联查询

由多模型类条件查询一模型类数据,语法如下

关联模型类名小写__属性名__条件运算符=值

注意:可以不填写__条件运算符部分,表示等于,结果和 sql 中的 inner join 相同

查询关联有名字中有“郭”的英雄的图书

books = Book.objects.filter(human__name__contains='郭')

由一模型类条件查询多模型类数据,语法如下

一模型类在多对应的模型类中关联属性名__一模型类中的属性__条件运算符=值

查询对应图书标题中含有“天龙”的人物

human = Human.objects.filter(book__title__contains='天龙')

自关联

对于省市区三级目录,这种类型的数据,表结构类似,并且数据有限,我们可以设置自关联的数据表,使一个字段关联本表的主键。可以定义一个 Area 类。

# 定义用于表示地区的类,存储省、市、区三级目录信息
class Area(models.Model):
	# 名称
	name=models.CharField(max_length=32)
	# 父级id
	parent=models.ForeignKey('self', null=True, blank=True)

执行迁移

python manage.py makemigrations
ython manage.py migrate

我们再写一个模板,用以在页面上显示地区信息

<!DOCTYPE html>
<html lang="utf-8">

<head>
    <title>area信息</title>
</head>

<body>
    当前地区:{{area.name}}
    <hr />
    上级地区:{{area.parent.name}}
    <hr />
    下级地区:
    <ul>
        {%for a in area.area_set.all%}
        <li>{{a.name}}</li>
        {%endfor%}
    </ul>
</body>

</html>

在 views.py 中添加 area 方法

def area(request):
    area = Area.objects.get(id=2)
    return render(request, 'bookapp/area.html', {'area': area})

在 urls.py 中配置url

path('area/', views.area, name='area')

在数据库中添加几条数据

INSERT INTO `django_second_day`.`bookapp_area`(`id`,`name`,`parent_id`) VALUES ( '1','黑龙江',NULL);
INSERT INTO `django_second_day`.`bookapp_area`(`id`,`name`,`parent_id`) VALUES ( '2','哈尔滨市',NULL);
INSERT INTO `django_second_day`.`bookapp_area`(`id`,`name`,`parent_id`) VALUES ( '3','平房区','2');
INSERT INTO `django_second_day`.`bookapp_area`(`id`,`name`,`parent_id`) VALUES ( '4','南岗区','2');
INSERT INTO `django_second_day`.`bookapp_area`(`id`,`name`,`parent_id`) VALUES ( '5','佳木斯市','1');

浏览器中访问

http://localhost:8000/bookapp/area/

就可以在页面上看到自关联显示的信息了。

模型类实例方法

  • str():在将对象转换成字符串时会被调用。
  • save():将模型类对象保存到数据表中,ORM 框架会自动转换成对应的insert或update语句。
  • delete():将模型对象从数据表中删除,ORM 框架会转换成对应的delete语句。

模型类的属性

objects:管理器,是 models.Manager 类型的对象,用于与数据库进行交互。当没有为模型类定义管理器时,Django 会为每一个模型类生成一个名为 objects 的管理器,自定义管理器后,Django 不再生成默认管理器 objects。

为模型类 Book 定义管理器 book_manager 语法如下:

class Book(models.Model):
	...
	book_manager = models.Manager()

管理器Manager

管理器是 Django 的模型进行数据库操作的接口,Django 应用的每个模型类都拥有至少一个管理器。Django 支持自定义管理器类,继承自 models.Manager。

自定义管理器类主要用于两种情况:

  • 修改原始查询集,重写 all() 方法
  • 向管理器类中添加额外的方法,如向数据库中插入数据。
  1. 修改原始查询集,重写 all() 方法。

打开 bookapp/models.py 文件,定义 BookManager

# 图书类管理器
class BookManager(models.Manager):
	def all(self):
		# 查询没有被删除的图书信息,重写 all() 方法
		return super().all().filter(isDelete=False)

在模型类 Book 中定义管理器

class Book(models.Model):
    ...
    books = BookManager()
  1. 在管理器中添加add_book方法
class BookManager(models.Manager):
    ...
    #创建模型类对象,接收要添加的信息
    def add_book(self, title, pub_date):
        #创建模型类对象self.model可以获得模型类
        book = self.model()
        book.title = title
        book.pub_date = pub_date
        book.read=0
        book.isDelete = False
        # 将数据插入进数据表
        book.save()
        return book

调用方式如下

book=Book.books.add_book("add", date(2001,1,1))

元选项

在模型类中定义类Meta,用于设置元信息,如使用db_table自定义表的名字。

表的默认名称是:

<app_name>_<model_name>
例:
bookapp_book

我们修改Book模型类生成的数据表名为book,在Book模型类中添加如下内容,代码如下

class Book(models.Model):
	...
	# 定义元选项
	class Meta:
		db_table='book'

结语

这篇内容断断续续写了好几天,还要加紧学习!
如果你发现我的文章哪里有错误或者有什么好的想法可以联系我,我们一起学习共同进步,我的邮箱地址是[email protected]

let’s do more of those!

发布了26 篇原创文章 · 获赞 2 · 访问量 2334

猜你喜欢

转载自blog.csdn.net/qq_42909545/article/details/103375107