Python入门自学进阶-Web框架——5Django的Model,即ORM对象关系映射
Posted kaoa000
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门自学进阶-Web框架——5Django的Model,即ORM对象关系映射相关的知识,希望对你有一定的参考价值。
Django是MTV模型,其中的M,即Model,是与数据库打交道的一层。
django默认支持sqlite,mysql, oracle,postgresql数据库。
<1> sqlite
django默认使用sqlite的数据库,默认自带sqlite的数据库驱动 , 引擎名称:django.db.backends.sqlite3
<2> mysql
引擎名称:django.db.backends.mysql
数据库的配置在settings.py中,前面讲过,如下:
如果要修改为mysql:
ORM(对象关系映射)
用于实现面向对象编程语言里不同类型系统的数据之间的转换,换言之,就是用面向对象的方式去操作数据库的创建表以及增删改查等操作。
优点: 1 ORM使得我们的通用数据库交互变得简单易行,而且完全不用考虑该死的SQL语句。快速开发,由此而来。
2 可以避免一些新手写sql语句带来的性能问题。
缺点:1 性能有所牺牲,不过现在的各种ORM框架都在尝试各种方法,比如缓存,延迟加载等来减轻这个问题。效果很显著。
2 对于个别复杂查询,ORM仍然力不从心,为了解决这个问题,ORM一般也支持写raw sql。
3 通过QuerySet的query属性查询对应操作的sql语句
所谓ORM,就是在程序中定义的一个类,对应了数据库中的一个表,类的一个实例,即一个对象,则对应了表中的一条记录。对类和对象的操作,会映射为对表及记录的操作。
创建单表
在应用目录下的models.py中创建类,类名+相关字段
一般加一个__str__(self)方法,在打印类时,即print()时,不是打印内存地址,而是调用这里定义的__str__(self),显示我们自定义的内容。
类要继承models.Model类
字段定义使用models.CharField(max_length=)等方法创建,还有IntegerField(),等。
类定义好以后,要在终端窗口执行命令生成表
python manage.py makemigrations # 类发生改变,根据改变生成执行方案
python manage.py migrate # 在数据库中生成相应的表,表名称为应用名_类名
表生成后,可以操作表了
class UserInfo(models.Model): username = models.CharField(max_length=64) sex = models.CharField(max_length=12) email = models.CharField(max_length=64)
python manage.py makemigrations
python manage.py migrate
此时,就创建了表:app01_userinfo,表名是大小写不敏感的。
单表的增删改查:
增加,即插入数据,两种方式——create和save
from django.db import models
# Create your models here. 通过create方法增加(插入)数据
class UserInfo(models.Model):
username = models.CharField(max_length=64)
sex = models.CharField(max_length=12)
email = models.CharField(max_length=64)
UserInfo.objects.create(
username="aaaaa",
sex="male",
email="123@123.com"
) # ,使用create方法,通过键值对的形式
UserInfo.objects.create(**"username":"bbb","sex":"famale","email":"345@sd.com")
# 使用create方法,通过字典的方式,字典前面加**,推荐这种方式
#下面是通过save方法增加(插入)数据
class UserInfo1(models.Model):
def __init__(self,username,sex,email):
self.username = username
self.sex = sex
self.email = email
username = models.CharField(max_length=64)
sex = models.CharField(max_length=12)
email = models.CharField(max_length=64)
user1 = UserInfo1("ddd","male","ggg@com.sp") #生成带数据的实例,直接save方法保存
user1.save()
user2 = UserInfo1() # 生成空实例,修改属性,在save方法保存
user2.username = "fff"
user2.sex = "male"
user2.email = "ooo@kk.com"
user2.save()
查:filter()方法和get()方法
删:delete()方法
改:update()方法或save()方法
#查找,通过filter方法,获得所有字段
UserInfo.objects.filter(username="aaa")
#查找某一字段,使用values()方法
UserInfo.objects.filter(username="aaa").values("sex")
# 返回的结果是一个列表["sex":"male",,]
#删除,需要先查找,再通过delete()方法删除
UserInfo.objects.filter(sex="male").delete()
#改一,即更新,也需要先找到,然后通过update()方法修改
UserInfo.objects.filter(username="aaa").update(sex="male")
#改二,先通过get找打需要修改的记录,通过对象属性进行修改,然后save()保存修改
user3 = UserInfo.objects.get(username="bbb")
user3.email = "uuu@so.com"
user3.save()
#删除和修改的前提都是先找到对应记录
注意:
<1> 改一的修改不能用get,只能使用filter的原因是:update是QuerySet对象的方法,get返回的是一个model对象,它没有update方法,而filter返回的是一个QuerySet对象(filter里面的条件可能有多个条件符合,比如username='alvin',可能有两个username='alvin'的行数据)。
<2>在“插入和更新数据”中,模型的save()方法,这个方法会更新一行里的所有列。 而某些情况下,我们只需要更新行里的某几列。
save()方法更新了不仅仅是明面上修改值的列,还更新了所有的列。 若除了明面上修改列值以外的其他列有可能会被其他的进程改动的情况下,只更改相应列显然是更加明智的。更改某一指定的列,我们可以调用结果集(QuerySet)对象的update()方法,与之等同的SQL语句变得更高效,并且不会引起竞态条件。
此外,update()方法对于任何结果集(QuerySet)均有效,这意味着你可以同时更新多条记录。update()方法会返回一个整型数值,表示受影响的记录条数。
总结一下:
# 查询相关API:
# <1>filter(**kwargs): 它包含了与所给筛选条件相匹配的对象
# <2>all(): 查询所有结果
# <3>get(**kwargs): 返回与所给筛选条件相匹配的对象,返回结果有且只有一个,如果符合筛选条件的对象超过一个或者没有都会抛出错误。
#-----------下面的方法都是对查询的结果再进行处理:比如 objects.filter.values()--------
# <4>values(*field): 返回一个ValueQuerySet——一个特殊的QuerySet,运行后得到的并不是一系列 model的实例化对象,而是一个可迭代的字典序列
# <5>exclude(**kwargs): 它包含了与所给筛选条件不匹配的对象
# <6>order_by(*field): 对查询结果排序,反向排序,可以在字段前加-
# <7>reverse(): 对查询结果反向排序
# <8>distinct(): 从返回结果中剔除重复纪录
# <9>values_list(*field): 它与values()非常相似,它返回的是一个元组序列,values返回的是一个字典序列
# <10>count(): 返回数据库中匹配查询(QuerySet)的对象数量。
# <11>first(): 返回第一条记录
# <12>last(): 返回最后一条记录
# <13>exists(): 如果QuerySet包含数据,就返回True,否则返回False。
另外,Django的ORM是使用的惰性机制:
所谓惰性机制:即.all()或者.filter()等都只是返回了一个QuerySet(查询结果集对象),它并不会马上执行sql,而是当调用QuerySet的时候才执行。QuerySet的特点是可迭代、可切片。
创建多表:
使用外键进行关联。创建四个模型,即创建四张表:作者模型、作者详细信息模型、出版商模型、书籍模型
作者模型:一个作者有姓名。
作者详细模型:把作者的详情放到详情表,包含性别,email地址和出生日期,作者详情模型和作者模型之间是一对一的关系(one-to-one)(类似于每个人和他的身份证之间的关系),在大多数情况下我们没有必要将他们拆分成两张表,这里只是引出一对一的概念。
出版商模型:出版商有名称,地址,所在城市,省,国家和网站。
书籍模型:书籍有书名和出版日期,一本书可能会有多个作者,一个作者也可以写多本书,所以作者和书籍的关系就是多对多的关联关系(many-to-many),一本书只应该由一个出版商出版,所以出版商和书籍是一对多关联关系(one-to-many),也被称作外键。
对于一对多的关系,外键一般建在多的一方。
代码测试:
一、单表创建:在models.py中创建类Author,有一个字段name
from django.db import models
class Author(models.Model):
name = models.CharField(max_length=16)
def __str__(self):
return self.name
terminal中执行:python manage.py makemigrations
python manage.py migrate
在app01应用的urls.py中增加路由项:path('addauthor/',views_app01.addauthor),
编写模板addauthor.html和视图函数addauthor:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/app01/addauthor/" method="post">
<p>输入作者名称:</p>
<input type="text" name="authorname"><br>
% csrf_token %
<input type="submit" value="submit">
</form>
<hr>
<h1> addresult </h1>
<!--这里addresult显示是否增加成功 -->
</body>
</html>
def addauthor(req):
res = ""
if req.method == "POST":
authorname = req.POST.get("authorname",None)
res = models.Author.objects.create(**"name":authorname)
print(res)
#此处看一下create的返回结果是什么,打印显示是插入的作者名字
return render(req,"addauthor.html","addresult":res)
修改一下,增加一个字段
class Author(models.Model):
name = models.CharField(max_length=16)
bh = models.IntegerField(default=0)
def __str__(self):
return self.name
这里注意增加IntegerField()字段时,参数必须有default,否则报错,因为表中有数据了,添加字段,已有数据的这些字段要有默认值。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/app01/addauthor/" method="post">
<p>输入作者名称:</p>
<input type="text" name="authorname"><br>
<input type="text" name="authorbh"><br>
% csrf_token %
<input type="submit" value="submit">
</form>
<hr>
<h1> addresult </h1>
</body>
</html>
def addauthor(req):
res = ""
if req.method == "POST":
authorname = req.POST.get("authorname",None)
authorbh = req.POST.get("authorbh",None)
authorbh = int(authorbh)
res = models.Author.objects.create(**"name":authorname,"bh":authorbh)
print(res)
return render(req,"addauthor.html","addresult":res)
create方法返回的值的类型为<class 'app01.models.Author'>,即插入成功,返回的是一个Author实例。前端直接使用addresult,实际是调用了Author的__str__()方法,直接显示名称,可以使用点号,addresult.name和addresult.bh显示全部信息。
在测试过程中,即修改对象模型Author的过程中,增加一个字段,删除原来的app01_author表后,使用migrations和migrate命令一直无法重新生成表,经过查询,这需要删除一些缓存以及数据库中的一些记录:
一、删除了app对应目录下的数据库对应的文件和缓存文件:
migrations/ __pycache__/
二、删除app下面目录migrations下面除了__init__.py其他的所有文件
三、最后,删除migrations中关于你的app的同步数据数据库记录
delete from django_migrations where app=‘yourappname’;
重新执行生成数据库命令:
python manage.py makemigrations 应用名 #可以只对指定的应用生成
python manage.py migrate
二、多表创建
一对一关联表的创建,表中使用的是ForeignKey,模型中使用的是OneToOneField:
在models.py中增加类:
class AuthorDetail(models.Model):
sex = models.BooleanField(max_length=1,choices=((0,'男'),(1,'女'),))
email = models.EmailField()
address = models.CharField(max_length=60)
birthday = models.DateField()
author = models.OneToOneField("Author")
# 这个author字段是一个外键,实现与Author表的一对一关系
执行生成表命令,结果在makemigrations时,提示
从其错误提示看,是缺少on_delete参数。也就是,当创建多张表时,表与表之间是有关联的,这里就是Author和AuthorDetail有关联,通过 AuthorDetail的author字段关联起来,这就涉及到当AuthorDetail表中删除记录时,Author表怎么处理,查了一下,基本有两种:models.CASCADE,级联删除和DO_NOTHING,什么也不做。我修改如下:
author = models.OneToOneField(Author,on_delete=models.CASCADE)
然后就生成了相关表。
关联字段author在表中的字段名变为author_id,且是外键(ForeignKey)。
进行增加和删除的测试:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--第一个form用来增加或查询作者,即查询author表中是否有相应名称的作者,有查询出来,无则增加 -->
<form action="/app01/addauthor/" method="post">
<p>输入作者名称:</p>
<input type="text" name="authorname"><br>
<input type="text" name="authorbh"><br>
% csrf_token %
<input type="submit" value="submit">
</form>
<hr>
<!-- 第二个form,根据增加或查询到的作者,添加详细信息-->
% if addresult != '' %
<h1> addresult.name tishi </h1>
<form action="/app01/adddetail/" method="post">
姓名:<input type="text" name="authorname" value=" addresult.name "><br>
性别:<input type="radio" name="sex" value="0">男
<input type="radio" name="sex" value="1">女<br>
邮箱:<input type="text" name="email"><br>
地址:<input type="text" name="address"><br>
生日:<input type="date" name="birthday"><br>
% csrf_token %
<input type="submit" value="增加作者详情">
</form>
% endif %
<hr>
<!--第三个form,测试根据作者名称删除作者,主要测试级联删除,在删除author表中记录的同时,也应该删除authordetail中的记录 -->
% if addresult != '' %
<h1> addresult.name tishi </h1>
<form action="/app01/deleteauthor/" method="post">
<input type="text" name="authorname" value=" addresult.name ">
% csrf_token %
<input type="submit" value="delete">
</form>
% endif %
</body>
</html>
路由项:
path('addauthor/',views_app01.addauthor),
path('adddetail/',views_app01.adddetail),
path('deleteauthor/',views_app01.deleteauthor),
视图函数:
def addauthor(req):
res = ""
tishi = ""
if req.method == "POST":
authorname = req.POST.get("authorname",None)
authorbh = req.POST.get("authorbh",None)
print("======",type(authorbh))
if authorbh!="":
authorbh = int(authorbh)
try:
res = models.Author.objects.get(name=authorname)
tishi = "查询到作者"
except Exception as e:
res = models.Author.objects.create(**"bh":authorbh,"name":authorname,)
tishi = "增加作者"
return render(req,"addauthor.html","addresult":res,"tishi":tishi)
def adddetail(req):
if req.method == "POST":
authorname = req.POST.get("authorname",None)
sex = req.POST.get("sex",None)
email = req.POST.get("email",None)
address = req.POST.get("address",None)
birthday = req.POST.get("birthday",None)
author = models.Author.objects.get(name=authorname)
adddetailres = models.AuthorDetail.objects.create(sex=sex,email=email,address=address,birthday=birthday,author=author)
return render(req,"adddetail.html","author":author,"adddetail":adddetailres)
def deleteauthor(req):
if req.method == "POST":
authorname = req.POST.get("authorname", None)
res = models.Author.objects.filter(name=authorname).delete()
print(res)
# 这里打印的是(2, 'app01.AuthorDetail': 1, 'app01.Author': 1),一个元组
return render(req,"deleteauthor.html")
<!--adddetail.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>作者姓名: author.name </p>
<hr>
性别: adddetail.sex <br>
邮箱: adddetail.email <br>
地址: adddetail.email <br>
生日: adddetail.birthday
<hr>
</body>
</html>
<!--deleteauthor.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
delete ok
</body>
</html>
测试页面:
本次测试了一对一多表的生成,测试了增加记录create方法的两种方式,测试了get和filter两种方法查询,测试了delete方法删除,删除的级联也起作用了。
做一个删除authordetail记录的测试:
res = models.Author.objects.filter(name=authorname)[0]
deldetail = models.AuthorDetail.objects.filter(author=res).delete()
此时只删除了authordetail记录,没有级联删除author的级联,所以模型中:
author = models.OneToOneField(Author,on_delete=models.CASCADE)
这一句,是说明定义这个字段所关联的表(即Author表中记录)删除时,此表记录一起删除,反向不起作用。
一对多关联表多表创建,模型中使用models.ForeignKey(),表中使用ForeignKey:
class Publisher(models.Model):
name = models.CharField(max_length=30,verbose_name="名称")
address = models.CharField("地址",max_length=60)
city = models.CharField("城市",max_length=60)
state_province = models.CharField(max_length=10)
country = models.CharField(max_length=50)
website = models.URLField()
def __str__(self):
return self.name
#
class Book(models.Model):
title = models.CharField(max_length=100)
publisher = models.ForeignKey(Publisher) # 一对多,表中字段是publisher_id,Django自动处理的
publication_date = models.DateField()
price = models.DecimalField(max_digits=5,decimal_places=2,default=10)
def __str__(self):
return self.title
一对多关系,Publisher是一,Book是多,一个出版社可以出版(对应)多本书,在多的一方,即Book中定义models.ForeignKey,即定义一对多字段,外键。
使用命令行生成数据库表时,又出现了上面的问题:
也就是有外键的情况下,定义的这个外键需要有on_delete这个选项,来定义删除时的级联操作,依然将其定义为models.CASCADE,重新生成:
提示了一个合并的错误,在authordetail表中有一条记录,其author_id为14,但是在author表中没有id为14的记录,删除authordetail表中这条记录,合并成功。
这里主要是要注意字段参数,关于CharField参数,其中有verbose_name,这相当于一个别名,从网上搜索的知识点看,可以如下使用:
name = models.CharField(max_length=12,verbose_name="名字") 或者
name = models.CharField("名字",max_length=12)
网上的一个例子说明
depot_name = models.CharField(
u'设备库房名称',
blank=True,
max_length=20,
null=True,
# default='',
help_text='显示在下方吗',
)
help_text是提示信息,u’设备库房名称‘,是将depot_name这个英文名重写(这句话说的很模糊,应该就是verbose_name的值,在后台管理页面中也许会将depot_name显示成设备库房名称,相当于一个详细说明,留待以后验证),blank=True是允许表单验证为空,null=True是允许数据库这个值为空。default应该是插入表中时的默认值,即没有输入其他值时,使用这个默认值。
另外一个解释的例子:
对表的操作,添加记录,添加book时,对于出版社字段,可以直接写出版社id,或者使用一个出版社对象。
publisher添加,属于单表添加记录:
def addpublisher(req):
results = ""
if req.method == "POST":
p_name = req.POST.get("name",None)
p_address = req.POST.get("address",None)
p_city = req.POST.get("city", None)
p_state_province = req.POST.get("state_province", None)
p_country = req.POST.get("country", None)
p_website = req.POST.get("website", None)
print(req.POST)
send_dic =req.POST.dict()
#将QueryDict转为普通的dict,QueryDict的值是列表
print(send_dic)
send_dic.pop("csrfmiddlewaretoken")
#POST中含有的csrfmiddlewaretoken不在数据模型中,删除
p_obj = models.Publisher(**send_dic)
p_obj.save()
results = "添加成功"
return render(req,"addpublisher.html","results":results)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/app01/addpublisher/" method="post">
name:<input type="text" name="name"><br>
address:<input type="text" name="address"><br>
ciry:<input type="text" name="city"><br>
state_province:<input type="text" name="state_province"><br>
country:<input type="text" name="country"><br>
website:<input type="text" name="website"><br>
% csrf_token %
<input type="submit" value="增加">
</form>
<hr>
% if results != "" %
% for item in request.POST.items %
item.1 ::
% endfor %
<br>数据添加成功!
% endif %
</body>
</html>
Book添加,有关联表:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/app01/addbook/" method="post">
<input type="text" name="title"><br>
<input type="date" name="publication_date"><br>
<input type="text" name="price" oninput="clearNoNum(this);"><br>
<select name="publisher">
% for item in idname %
<option value=" item.0 "> item.1 </option>
% endfor %
</select>
% csrf_token %
<input type="submit" value="增加">
</form>
<hr>
</body>
</html>
def addbook(req):
results = ""
p_idname = models.Publisher.objects.all().values_list("id","name")
print(p_idname)
if req.method == "POST":
title = req.POST.get("title",None)
publication_date =req.POST.get("publication_date",None)
price = float(req.POST.get("price",None))
publisher_id = int(req.POST.get("publisher",None))
publisher = models.Publisher.objects.filter(id=req.POST.get("publisher",None))[0]
# b_dict = "title":title,"publication_date":publication_date,"price":price,"publisher_id":publisher_id
# 上面的b_dict是直接使用publisher_id,即表的字段名,其值也是出版社的id值
# b_dict = "title": title, "publication_date": publication_date, "price": price, "publisher": publisher
# 上面的b_dict是使用一个出版社的对象,使用publisher,值是一个对象
print("===============")
print(title)
print(publication_date)
print(price)
print(publisher_id)
print(publisher)
# re = models.Book.objects.create(**b_dict)
print("===============")
# print(re)
res_obj = models.Book()
res_obj.title = title
res_obj.publication_date = publication_date
res_obj.price = price
res_obj.publisher = publisher
res_obj.save()
#使用save方法,只能使用publisher对象
return render(req,"addbook.html","idname":p_idname,"results":results)
多对多关联表多表创建,模型中使用models.ManyToManyField(),将会创建第三张表:
修改Book模型,增加多对多,一本书可以有多个作者,一个作者可以写多本书:
class Book(models.Model):
title = models.CharField(max_length=100)
authors = models.ManyToManyField(Author) # 多对多,外键可以放到任意一方
publisher = models.ForeignKey(Publisher,on_delete=models.CASCADE) # 一对多,表中字段是publisher_id,Django自动处理的
publication_date = models.DateField()
price = models.DecimalField(max_digits=5,decimal_places=2,default=10)
def __str__(self):
return self.title
重新生成数据库,发现多了一个表:
当然,还可以在Author模型中增加多对多字段,在Author中增加
books = models.ManyToManyField(Book)
给一本书添加作者,可以有多个作者,可以如下:先生成几个作者对象,然后生成书对象
author1 = models.Author.objects.get(id=3)
author2 = models.Author.objects.get(id=4)
book = models.Book.objects.filter(id=5)[0]
然后利用书对象的authors属性,即多对多字段,这个其实是一个作者对象的列表,利用add方法添加:
book.authors.add(author1)
book.authors.add(author2)
或者book.authors.add(author1,author2)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/app01/book2author/" method="post">
<select name="book">
% for item in books %
<option value=" item.0 "> item.1 </option>
% endfor %
</select>
<select name="authorset" multiple="multiple">
% for item in authors %
<option value=" item.0 "> item.1 </option>
% endfor %
</select>
% csrf_token %
<input type="submit" value=" + ">
</form>
</body>
</html>
def book2author(req):
book_select = models.Book.objects.all().values_list("id", "title")
author_select = models.Author.objects.all().values_list("id","name")
if req.method == "POST":
bookid = req.POST.get("book",None)
authorsid = req.POST.getlist("authorset", None)
# request中的get方法只能获取单个值,
# 同一name属性有多个值的情况(如select和checkbox的复选框)需要使用getlist方法,
book_obj = models.Book.objects.get(id=bookid)
authors_obj = models.Author.objects.filter(id__in = authorsid)
# 判断id值在一个列表中,使用字段名__in=[列表]
# 同样还有__gt大于、__gte大于等于、__lt小于、__lte小于等于
print(book_obj)
print(authors_obj)
book_obj.authors.add(*authors_obj)
return render(req,"book2author.html","books":book_select,"authors":author_select)
运行:
然后就在多对多表app01_book_author中增加了记录。
这里的多对多的关系表app01_book_author,是通过在Book中的ManyToManyField建立的,也可以不通过这个字段,而自己手工建立,这时就要新建一个数据模型。
关于外键ForeignKey()参数问题,参数可以是一个model类,即类名,也可以是加上双引号的类名,一般要加双引号,直接类名时,相应的类必须在这个类的上面,否则找不到。
以上是关于Python入门自学进阶-Web框架——5Django的Model,即ORM对象关系映射的主要内容,如果未能解决你的问题,请参考以下文章
Python入门自学进阶-Web框架——20Django其他相关知识2
Python入门自学进阶-Web框架——3Django的URL配置
Python入门自学进阶-Web框架——21DjangoAdmin项目应用