day70学习笔记之ORM一对一、多对多表结构和跨站请求相关
1 一对一表结构介绍
1.1 什么时候用一对一
当一张表的某一些字段查询的比较频繁,另外一些字段查询的不是特别频繁,把不怎么常用的字段单独拿出来做成一张表,然后用一对一表结构关联起来。这样,既保证数据都完整的保存下来,又能保证大部分的内容检索更快。
示例如下:
#在models.py中,创建如下表格类 from django.db import models # 作者 class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() phone = models.IntegerField() books = models.ManyToManyField(to="Book", related_name="authors") detail = models.OneToOneField(to="AuthorDetail") #一对一 def __str__(self): return self.name # 作者详情 class AuthorDetail(models.Model): # 爱好 hobby = models.CharField(max_length=32) # 地址 addr = models.CharField(max_length=128)
一对一查询:
import os if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ormday70.settings") import django django.setup() from app01 import models # 一对一的查询 author_obj = models.Author.objects.get(id=1) obj = author_obj.detail #拿到一个与id为1的作者一对一关联的AuthorDetail对象 print(obj.hobby, obj.addr)
注意,如果往作者表(Author)里面添加数据时,一个作者只能使用一个AuthorDetail表中的id,并且这个id不能再被其他作者对象使用。
2 ORM多对多表创建的三种方式介绍
2.1 ORM自动创建第三张表
首先在app01文件夹下的models.py中创建以下表格类:
# 作者 class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() phone = models.IntegerField() books = models.ManyToManyField(to="Book", related_name="authors") #通过ORM多对多方式自动创建第三张表(app01_author_books) detail = models.OneToOneField(to="AuthorDetail") def __str__(self): return self.name class Book(models.Model): title = models.CharField(max_length=32) publish_date = models.DateField(auto_now_add=True) price = models.DecimalField(max_digits=5, decimal_places=2) # 创建外键,关联publish publisher = models.ForeignKey(to="Publisher") def __str__(self): return self.title
在测试程序中操作表格:
import os import sys if __name__ == '__main__': os.environ.setdefault("DJANGO_SETTINGS_MODULE", "ormday70.settings") import django django.setup() from app01 import models # 在app01 里面 查询 id是1的作者关联的书 from app01 import models ret = models.Author.objects.get(id=1).books.all() # 从作者关联的书里面移除id是1的书 models.Author.objects.get(id=1).books.remove(1) print(ret)
2.2 手动创建第三张表, 利用外键分别关联作者和书
特点:关联查询比较麻烦,因为没办法使用ORM提供的便利方法
首先在app02文件夹下的models.py中创建以下表格类:
# 书 class Book(models.Model): title = models.CharField(max_length=32) publish_date = models.DateField(auto_now_add=True) price = models.DecimalField(max_digits=5, decimal_places=2) # 创建外键,关联publish publisher = models.ForeignKey(to="Publisher") def __str__(self): return self.title # 作者 class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() phone = models.IntegerField() detail = models.OneToOneField(to="AuthorDetail") def __str__(self): return self.name # 自己动手 创建作者和书关联的第三张表 # 此时 在ORM层面 作者和书就没有多对多的关系了 class Author2Book(models.Model): id = models.AutoField(primary_key=True) # 作者id author = models.ForeignKey(to="Author") #通过外键关联作者表 # 书id book = models.ForeignKey(to="Book") #通过外键关联书表 class Meta: # 建立唯一约束 unique_together = ("author", "book") #防止重复信息
在测试程序中操作表格:
from app02 import models ret = models.Author2Book.objects.filter(author_id=1).values_list("book_id") #拿到一个元组列表 # id是1的作者关联的书的id print(ret) #结果:<QuerySet [(1,),(2,),(3,)]> ret = [i[0] for i in ret] #列表生成式 # 查书这张表 ret = models.Book.objects.filter(id__in=ret) print(ret) #得到一个QuerySet列表,里面包含对应ID的书籍对象
2.3 手动创建第三张表, 使用ORM 的ManyToManyFiled()
特点:使用此种方式创建多对多表的时候,没有 add() remove() 等快捷方法
首先在app03文件夹下的models.py中创建以下表格类:
# 书 class Book(models.Model): title = models.CharField(max_length=32) publish_date = models.DateField(auto_now_add=True) price = models.DecimalField(max_digits=5, decimal_places=2) # 创建外键,关联publish publisher = models.ForeignKey(to="Publisher") def __str__(self): return self.title # 作者 class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() phone = models.IntegerField() # 通过through,through_fields来指定使用我创建的第三张表来构建多对多的关系 books = models.ManyToManyField(to="Book", through="Author2Book", through_fields=("author", "book",)) # through表示通过哪张表和书籍建立多对多关系 # through_fields表示通过哪些字段建立关联关系, detail = models.OneToOneField(to="AuthorDetail") def __str__(self): return self.name # 自己动手 创建作者和书关联的第三张表 # 此时 在ORM层面 class Author2Book(models.Model): id = models.AutoField(primary_key=True) # 作者id author = models.ForeignKey(to="Author") # 书id book = models.ForeignKey(to="Book") # memo memo = models.CharField(max_length=64, null=True) class Meta: # 建立唯一约束 unique_together = ("author", "book")
注意,through_fields中第一个元素确定方式如下:
由于在第三张表(Author2Book)中只能通过author字段找到含有多对多字段(books)所在的表(Author),因此through_fields属性中第一个元素为第三张表中的author,第二个元素就是剩下的外键字段(book)
在测试程序中操作表格:
from app03 import models ret = models.Author.objects.get(id=1).books.all() print(ret) # 从作者关联的书里面移除id是1的书 # 没有django ORM 封装的那些快捷方式了,我们要自己亲自改第三张表 models.Author2Book.objects.get(author_id=1, book_id=1).delete()
如何选择使用哪种方式建立多对多关系?
1. 如果第三张表没有额外的字段,就用第一种
2. 如果第三张表有额外的字段,就用第三种或第二种
3 ORM训练题
3.1 表结构设计
在app01文件夹下的models.py中建立如下表格:
from django.db import models # Create your models here. # 出版社 class Publisher(models.Model): name = models.CharField(max_length=32) city = models.CharField(max_length=32) def __str__(self): return self.name # 书 class Book(models.Model): title = models.CharField(max_length=32) publish_date = models.DateField(auto_now_add=True) price = models.DecimalField(max_digits=5, decimal_places=2) # 创建外键,关联publish publisher = models.ForeignKey(to="Publisher") def __str__(self): return self.title # 作者 class Author(models.Model): name = models.CharField(max_length=32) age = models.IntegerField() phone = models.IntegerField() books = models.ManyToManyField(to="Book", related_name="authors") detail = models.OneToOneField(to="AuthorDetail") def __str__(self): return self.name # 作者详情 class AuthorDetail(models.Model): # 爱好 hobby = models.CharField(max_length=32) # 地址 addr = models.CharField(max_length=128)
3.2 练习题
1. 查找所有书名里包含沙河的书:
ret = models.Book.objects.filter(title__contains="沙河")
2. 查找所有出版日期是2018年的书:
ret = models.Book.objects.filter(publish_date__year=2017)
3. 查找价格大于10元的书:
ret = models.Book.objects.filter(price__gt=10)
4. 找到地址为沙河的出版社:
ret = models.Publisher.objects.filter(city="沙河")
5. 查所有书关联的出版社名字:
ret = models.Book.objects.all().values_list("publisher__name") print(ret.distinct()) # 对QuerySet去重
6. 将所有的书按照价格倒叙排序:
#方法1,order_by方法默认从小到大排序,因此在order_by的基础上使用reverse() ret = models.Book.objects.all().order_by("price").reverse() #方法2,使用负号“-” ret = models.Book.objects.all().order_by("-price")
7. 查询书名是沙河异闻录的书 的出版社的city:
ret = models.Book.objects.filter(title="沙河异闻录").values("publisher__city")
8. 查询书名是沙河异闻录的书的作者的爱好(跨两张表查询)
ret = models.Book.objects.filter(title="沙河异闻录").values("authors__detail__hobby")
4 跨站请求伪造(CSRF)和csrf_token使用
CSRF攻击攻击原理及过程如下:
1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
4.1 Django中内置的中间件
Django中内置了一个专门处理csrf问题的中间件:django.middleware.csrf.CsrfViewMiddleware
假设目前有两个网站,网站A(官方网站),网站B(恶意网站),网页具体代码如下:
网站A:
1. login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="/login/" method="post"> {% csrf_token %} <p> 用户名: <input type="text" name="username"> </p> <p>密码: <input type="password" name="pwd"> </p> <p> <input type="submit" value="登录"> </p> </form> </body> </html>
2. transfer.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>正经的网站</h1> <form action="/transfer/" method="post"> {% csrf_token %}<!--加上这一句--> <p> 转出: <input type="text" name="from"> </p> <p> 转入: <input type="text" name="to"> </p> <p> 金额: <input type="text" name="money"> </p> <p> <input type="submit" value="转账"> </p> </form> </body> </html>
3. 在网站A的views.py中有以下代码:
from django.shortcuts import render, redirect, HttpResponse # Create your views here. def login(request): if request.method == "POST": # 取出数据 user = request.POST.get("username") pwd = request.POST.get("pwd") if user == "小黑" and pwd =="1122": return render(request, "transfer.html") return render(request, "login.html") # 转账 def transfer(request): if request.method == "POST": from_ = request.POST.get("from") to_ = request.POST.get("to") money = request.POST.get("money") print("{} 给 {} 转了 {}钱".format(from_, to_, money)) return HttpResponse("转账成功!") return render(request, "transfer.html")
4. 在网站A的settings.py文件中的 MIDDLEWARE 列表中加入以下代码
'django.middleware.csrf.CsrfViewMiddleware',
注:设置网站A项目占用8000端口
网站B:
1. transfer.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>钓鱼的网站</h1> <form action="http://127.0.0.1:8000/transfer/" method="post"> <p> 转出: <input type="text" name="from"> </p> <p> 转入: <input type="text" name=""> <input type="text" name="to" style="display: none" value="哪吒"> </p> <p> 金额: <input type="text" name="money"> </p> <p> <input type="submit" value="转账"> </p> </form> </body> </html>
2. 在网站B的views.py中有以下代码:
from django.shortcuts import render # Create your views here. def transfer(request): return render(request, "transfer.html")
注:设置网站B项目占用8888端口
4.1.1 正常流程
用户在浏览器中输入http://127.0.0.1:8000/login/时,跳转到网站A的登录界面(login.html),输入用户名和密码并验证成功以后,跳转到网站A的转账界面(transfer.html),填写完信息并提交给后台。
4.1.2 非正常流程
用户在浏览器中输入http://127.0.0.1:8000/login/时,跳转到网站A的登录界面(login.html),输入用户名和密码并验证成功以后,此时由于某种原因,触发了http://127.0.0.1:8888/transfer/请求,并打开了网站B的转账界面(transfer.html),当用户填完转账信息提交后(不管用户转入那一栏输入什么,最后默认提交的数据都为 哪吒,这是一个潜在危险),由于网站A使用了Django中内置的处理csrf问题的中间件,所以该转账信息提交后,后台会验证其为非法请求,对提交的数据不做处理,并返回错误信息。

这个中间件主要做了以下事情:
1. 当网站A的views.py文件中login函数执行return render(request, "transfer.html")语句返回页面的时候,在transfer.html页面中塞了一个隐藏的input标签:
<input type="hidden" name="csrfmiddlewaretoken" value="ayJQtggJYxpBPXT8qMOqKjs5exWPgpM2kQ54t1q1hcAvBXzez81ob0KMPze8hC7H">
即将页面中的 {% csrf_token %} 替换为上面的input标签中的内容,当用户填写完转账信息后,提交给后台时,首先会自动判断input标签的value值是否和当时render请求塞入的input标签中的value值一致,如果一致,则执行对应的处理函数,否则返回错误信息。
每次render返回的transfer.html页面中的input标签的value值都不一样
注意:{% csrf_token %} 必须放在页面form标签内部

浙公网安备 33010602011771号