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
View Code

在测试程序中操作表格:

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")  #防止重复信息
View Code

 在测试程序中操作表格:

    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")
View Code

注意,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)
View Code

 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>
View Code

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>
View Code

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")
View Code

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>
View Code

2.  在网站B的views.py中有以下代码:

from django.shortcuts import render

# Create your views here.


def transfer(request):
    return render(request, "transfer.html")
View Code

 注:设置网站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标签内部

posted @ 2019-09-23 19:50  陌上の花开  阅读(190)  评论(0)    收藏  举报