Django知识点笔记(3)

简介: Django知识点笔记(3)

修改记录

(1) 使用save更新数据

student = Student.objects.filter(name='刘德华').first()
print(student)
student.age = 19
student.classmate = "303"
# save之所以能提供给我们添加数据的同时,还可以更新数据的原因?
# save会找到模型的字段的主键id的值,
# 主键id的值如果是none,则表示当前数据没有被数据库,所以save会自动变成添加操作
# 主键id有值,则表示当前数据在数据库中已经存在,所以save会自动变成更新数据操作
student.save()

(2)update更新(推荐)

使用模型类.objects.filter().update(),会返回受影响的行数

# update是全局更新,只要符合更新的条件,则全部更新,因此强烈建议加上条件!!!
student = Student.objects.filter(name="赵华",age=22).update(name="刘芙蓉",sex=True)
print(student)

删除记录

删除有两种方法

(1)模型类对象.delete

student = Student.objects.get(id=13)
student.delete()

(2)模型类.objects.filter().delete()

Student.objects.filter(id=14).delete()

代码:

# 1. 先查询到数据模型对象。通过模型对象进行删除
student = Student.objects.filter(pk=13).first()
student.delete()
# 2. 直接删除
ret = Student.objects.filter(pk=100).delete()
print(ret)
# 务必写上条件,否则变成了清空表了。ret = Student.objects.filter().delete()

创建关联模型

实例:我们来假定下面这些概念,字段和关系


班级模型: 班级名称、导员。


课程模型:课程名称、讲师等。


学生模型: 学生有姓名,年龄,只有一个班级,所以和班级表是一对多的关系(one-to-many);选修了多个课程,所以和课程表是多对多的关系(many-to-many)


学生详情:学生的家庭地址,手机号,邮箱等详细信息,和学生模型应该是一对一的关系(one-to-one)

from django.db import models
# Create your models here.
class Clas(models.Model):
    name = models.CharField(max_length=32, unique=True, verbose_name="班级名称")
    class Meta:
        db_table = "db_class"
class Course(models.Model):
    name = models.CharField(max_length=32, unique=True, verbose_name="课程名称")
    class Meta:
        db_table = "db_course"
class Student(models.Model):
    sex_choices = (
        (0, "女"),
        (1, "男"),
        (2, "保密"),
    )
    name = models.CharField(max_length=32, unique=True, verbose_name="姓名")
    age = models.SmallIntegerField(verbose_name="年龄", default=18)  # 年龄
    sex = models.SmallIntegerField(choices=sex_choices)
    birthday = models.DateField()
    # 一对多
    # on_delete= 关联关系的设置
    # models.CASCADE    删除主键以后, 对应的外键所在数据也被删除
    # models.DO_NOTHING 删除主键以后, 对应的外键不做任何修改
    # 反向查找字段 related_name
    clas = models.ForeignKey("Clas", on_delete=models.CASCADE)
    # 多对多
    # 建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
    courses = models.ManyToManyField("Course", db_table="db_student2course")
    # 一对一,使用同一对多
    stu_detail = models.OneToOneField("StudentDetail", on_delete=models.CASCADE)
    class Meta:
        db_table = "db_student"
    def __str__(self):
        return self.name
class StudentDetail(models.Model):
    tel = models.CharField(max_length=32)
    email = models.EmailField()
    description = models.TextField(null=True, verbose_name="个性签名")
    class Meta:
        db_table = "db_student_detail"

关联添加

(1)一对多与一对一

 stu = Student.objects.create(name="王五", age=23, sex=1, birthday="1991-11-12", clas_id=9, stu_detail_id=6)
  print(stu.name)
  print(stu.age)
  print(stu.sex)
  print(stu.clas_id)  # 6
  print(stu.stu_detail_id)  # 5
  print(stu.clas)  # 模型类对象
  print(stu.stu_detail)  # 模型类对象
  # 查询stu这个学生的班级名称
  print(stu.clas.name)
  # 查询stu这个学生的手机号
  print(stu.stu_detail.tel)

(2)多对多

 # stu = Student.objects.create(name="rain", age=33, sex=1, birthday="1996-11-12", clas_id=9, stu_detail_id=7)
    # (1) 添加多对多的数据
    # 添加多对多方式1
    c1 = Course.objects.get(title="思修")
    c2 = Course.objects.get(title="逻辑学")
    stu.courses.add(c1,c2)
    # 添加多对多方式2
    stu = Student.objects.get(name="张三")
    stu.courses.add(5,7)
    # 添加多对多方式3
    stu = Student.objects.get(name="李四")
    stu.courses.add(*[6,7])
    # (2) 删除多对多记录
   stu = Student.objects.get(name="李四")
   stu.courses.remove(7)
    # (3) 清空多对多记录:clear方法
    stu = Student.objects.get(name="rain")
    stu.courses.clear()
    # (4) 重置多对多记录:set方法
    stu = Student.objects.get(name="李四")
    stu.courses.set([5,8])
    # (5) 多对多记录查询: all
    # 查询李四所报课程的名称
    stu = Student.objects.get(name="李四")
    courses = stu.courses.all()
    courses = stu.courses.all().values("title")
    print(courses)  # <QuerySet [<Course: Course object (5)>, <Course: Course object (8)>]>1.

关联查询

基于对象查询(子查询)

    # **********************************  一对多查询
    # 查询张三所在班级的名称
    stu = Student.objects.get(name="张三")
    print(stu.clas.name)
    # 查询计算机科学与技术2班有哪些学生
    clas = Clas.objects.get(name="计算机科学与技术2班")
    # 反向查询方式1:
    ret = clas.student_set.all()  # 反向查询按表名小写_set
    print(ret) # <QuerySet [<Student: 张三>, <Student: 李四>]>
    # 反向查询方2:
    print(clas.student_list.all())  # <QuerySet [<Student: 张三>, <Student: 李四>]>
    # **********************************  一对一查询
    # 查询李四的手机号
    stu = Student.objects.get(name="李四")
    print(stu.stu_detail.tel)
    # 查询110手机号的学生姓名和年龄
    stu_detail = StudentDetail.objects.get(tel="110")
    # 反向查询方式1: 表名小写
    print(stu_detail.student.name)
    print(stu_detail.student.age)
    # 反向查询方式2: related_name
    print(stu_detail.stu.name)
    print(stu_detail.stu.age)
    # **********************************  多对多查询
    # 查询张三所报课程的名称
    stu = Student.objects.get(name="张三")
    print(stu.courses.all())  # QuerySet [<Course: 近代史>, <Course: 篮球>]>
    # 查询选修了近代史这门课程学生的姓名和年龄
    course = Course.objects.get(title="近代史")
    # 反向查询方式1: 表名小写_set
    print(course.student_set.all()) # <QuerySet [<Student: 张三>, <Student: 李四>]>
    # 反向查询方式2:related_name
   print(course.students.all())
   print(course.students.all().values("name","age")) # <QuerySet [{'name': '张三', 'age': 22}, {'name': '李四', 'age': 24}]>

(1)正向查询按字段

(2)反向查询按表名小写或者related_name

基于双下划线查询(join查询)

    # 查询张三的年龄
    ret = Student.objects.filter(name="张三").values("age")
    print(ret) # <QuerySet [{'age': 22}]>
    # (1) 查询年龄大于22的学生的姓名以及所在名称班级
    # select db_student.name,db_class.name from db_student inner join db_class on db_student.clas_id = db_class.id where db_student.age>22;
    # 方式1 : Student作为基表
    ret = Student.objects.filter(age__gt=22).values("name","clas__name")
    print(ret)
    # 方式2 :Clas表作为基表
    ret = Clas.objects.filter(student_list__age__gt=22).values("student_list__name","name")
    print(ret)
    # (2) 查询计算机科学与技术2班有哪些学生
    ret = Clas.objects.filter(name="计算机科学与技术2班").values("student_list__name")
    print(ret)  #<QuerySet [{'student_list__name': '张三'}, {'student_list__name': '李四'}]>
    # (3) 查询张三所报课程的名称
    ret = Student.objects.filter(name="张三").values("courses__title")
    print(ret) # <QuerySet [{'courses__title': '近代史'}, {'courses__title': '篮球'}]>
    # (4) 查询选修了近代史这门课程学生的姓名和年龄
    ret = Course.objects.filter(title="近代史").values("students__name","students__age")
    print(ret) # <QuerySet [{'students__name': '张三', 'students__age': 22}, {'students__name': '李四', 'students__age': 24}]>
    # (5) 查询李四的手机号
    ret = Student.objects.filter(name='李四').values("stu_detail__tel")
    print(ret)  # <QuerySet [{'stu_detail__tel': '911'}]>
    # (6) 查询手机号是110的学生的姓名和所在班级名称
    # 方式1
    ret = StudentDetail.objects.filter(tel="110").values("stu__name","stu__clas__name")
    print(ret) # <QuerySet [{'stu__name': '张三', 'stu__clas__name': '计算机科学与技术2班'}]>
    # 方式2:
    ret = Student.objects.filter(stu_detail__tel="110").values("name","clas__name")
    print(ret) # <QuerySet [{'name': '张三', 'clas__name': '计算机科学与技术2班'}]>

(1)正向关联按关联字段

(2)反向按表名小写或related_name

关联分组查询

# from django.db.models import Avg, Count, Max, Min
ret = Student.objects.values("sex").annotate(c = Count("name"))
print(ret) # <QuerySet [{'sex': 0, 'c': 1}, {'sex': 1, 'c': 3}]>
# (1)查询每一个班级的名称以及学生个数
ret = Clas.objects.values("name").annotate(c = Count("student_list__name"))
print(ret) # <QuerySet [{'name': '网络工程1班', 'c': 0}, {'name': '网络工程2班', 'c': 0}, {'name': '计算机科学与技术1班', 'c': 0}, {'name': '计算机科学与技术2班', 'c': 1}, {'name': '软件1班', 'c': 3}]>
# (2)查询每一个学生的姓名,年龄以及选修课程的个数
ret = Student.objects.values("name","age").annotate(c=Count("courses__title"))
print(ret) # <QuerySet [{'name': 'rain', 'c': 0}, {'name': '张三', 'c': 2}, {'name': '李四', 'c': 2}, {'name': '王五', 'c': 0}]>
ret = Student.objects.all().annotate(c=Count("courses__title")).values("name","age","sex","c")
# print(ret)
# (3) 每一个课程名称以及选修学生的个数
ret = Course.objects.all().annotate(c = Count("students__name")).values("title","c")
print(ret) # <QuerySet [{'title': '近代史', 'c': 2}, {'title': '思修', 'c': 0}, {'title': '篮球', 'c': 1}, {'title': '逻辑学', 'c': 1}, {'title': '轮滑', 'c': 0}]>
# (4)  查询选修课程个数大于1的学生姓名以及选修课程个数
ret = Student.objects.all().annotate(c=Count("courses__title")).filter(c__gt=1).values("name","c")
print(ret) # <QuerySet [{'name': '张三', 'c': 2}, {'name': '李四', 'c': 2}]>
# (5) 查询每一个学生的姓名以及选修课程个数并按着选修的课程个数进行从低到高排序
ret = Student.objects.all().annotate(c=Count("courses__title")).order_by("c").values("name","c")
print(ret)

六、ORM进阶

queryset特性

(1)可切片

使用Python 的切片语法来限制查询集记录的数目 。它等同于SQL 的LIMITOFFSET 子句。

>>> Article.objects.all()[:5]   # (LIMIT 5)
>>> Article.objects.all()[5:10]    # (OFFSET 5 LIMIT 5)

不支持负的索引(例如Article.objects.all()[-1])。通常,查询集 的切片返回一个新的查询集—— 它不会执行查询。

(2)可迭代

articleList=models.Article.objects.all()
for article in articleList:
    print(article.title)

(3)惰性查询

查询集 是惰性执行的 —— 创建查询集不会带来任何数据库的访问。你可以将过滤器保持一整天,直到查询集 需要求值时,Django 才会真正运行这个查询。

queryResult=models.Article.objects.all() # not hits database
print(queryResult) # hits database
for article in queryResult:
    print(article.title)    # hits database

一般来说,只有在“请求”查询集 的结果时才会到数据库中去获取它们。当你确实需要结果时,查询集 通过访问数据库来求值。 关于求值发生的准确时间。


(4)缓存机制


每个查询集都包含一个缓存来最小化对数据库的访问。理解它是如何工作的将让你编写最高效的代码。


在一个新创建的查询集中,缓存为空。首次对查询集进行求值 —— 同时发生数据库查询 ——Django 将保存查询的结果到查询集的缓存中并返回明确请求的结果(例如,如果正在迭代查询集,则返回下一个结果)。接下来对该查询集 的求值将重用缓存的结果。

请牢记这个缓存行为,因为对查询集使用不当的话,它会坑你的。例如,下面的语句创建两个查询集,对它们求值,然后扔掉它们:

queryset = Book.objects.all()
print(queryset) # hit database
print(queryset) # hit database

注:简单地打印查询集不会填充缓存。

这意味着相同的数据库查询将执行两次,显然倍增了你的数据库负载。同时,还有可能两个结果列表并不包含相同的数据库记录,因为在两次请求期间有可能有Article被添加进来或删除掉。为了避免这个问题,只需保存查询集并重新使用它:

queryset = Book.objects.all()
ret = [i for i in queryset] # hit database
print(queryset) # 使用缓存
print(queryset) # 使用缓存

何时查询集会被缓存?

  1. 遍历queryset时
  2. if语句(为了避免这个,可以用exists()方法来检查是否有数据)

所以单独queryset的索引或者切片都不会缓存。

queryset = Book.objects.all()
one = queryset[0] # hit database
two = queryset[1] # hit database
print(one)
print(two)

(5)exists()与iterator()方法

exists

简单的使用if语句进行判断也会完全执行整个queryset并且把数据放入cache,虽然你并不需要这些 数据!为了避免这个,可以用exists()方法来检查是否有数据:

 if queryResult.exists():
    #SELECT (1) AS "a" FROM "blog_article" LIMIT 1; args=()
        print("exists...")

iterator


当queryset非常巨大时,cache会成为问题。


处理成千上万的记录时,将它们一次装入内存是很浪费的。更糟糕的是,巨大的queryset可能会锁住系统 进程,让你的程序濒临崩溃。要避免在遍历数据的同时产生queryset cache,可以使用iterator()方法 来获取数据,处理完数据就将其丢弃。

objs = Book.objects.all().iterator()
# iterator()可以一次只从数据库获取少量数据,这样可以节省内存
for obj in objs:
    print(obj.title)
#BUT,再次遍历没有打印,因为迭代器已经在上一次遍历(next)到最后一次了,没得遍历了
for obj in objs:
    print(obj.title)

当然,使用iterator()方法来防止生成cache,意味着遍历同一个queryset时会重复执行查询。所以使 #用iterator()的时候要当心,确保你的代码在操作一个大的queryset时没有重复执行查询。


queryset的cache是用于减少程序对数据库的查询,在通常的使用下会保证只有在需要的时候才会查询数据库。 使用exists()和iterator()方法可以优化程序对内存的使用。不过,由于它们并不会生成queryset cache,可能 会造成额外的数据库查询。 


总结:在使用缓存机制还是生成器机制的选择上如果是,数据量大情况主要使用生成器;数据少使用次数多的情况使用缓存机制。

中介模型

处理类似搭配 pizza 和 topping 这样简单的多对多关系时,使用标准的ManyToManyField 就可以了。但是,有时你可能需要关联数据到两个模型之间的关系上。


例如,有这样一个应用,它记录音乐家所属的音乐小组。我们可以用一个ManyToManyField 表示小组和成员之间的多对多关系。但是,有时你可能想知道更多成员关系的细节,比如成员是何时加入小组的。


对于这些情况,Django 允许你指定一个中介模型来定义多对多关系。 你可以将其他字段放在中介模型里面。源模型的ManyToManyField 字段将使用through 参数指向中介模型。对于上面的音乐小组的例子,代码如下:

from django.db import models
class Person(models.Model):
    name = models.CharField(max_length=128)
    def __str__(self):              # __unicode__ on Python 2
        return self.name
class Group(models.Model):
    name = models.CharField(max_length=128)
    members = models.ManyToManyField(Person, through='Membership')
    def __str__(self):              # __unicode__ on Python 2
        return self.name
class Membership(models.Model):
    person = models.ForeignKey(Person)
    group = models.ForeignKey(Group)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)

既然你已经设置好ManyToManyField 来使用中介模型(在这个例子中就是Membership),接下来你要开始创建多对多关系。你要做的就是创建中介模型的实例:

>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")
>>> m1 = Membership(person=ringo, group=beatles,
...     date_joined=date(1962, 8, 16),
...     invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
[<Person: Ringo Starr>]
>>> ringo.group_set.all()
[<Group: The Beatles>]
>>> m2 = Membership.objects.create(person=paul, group=beatles,
...     date_joined=date(1960, 8, 1),
...     invite_reason="Wanted to form a band.")
>>> beatles.members.all()
[<Person: Ringo Starr>, <Person: Paul McCartney>]

与普通的多对多字段不同,你不能使用addcreate和赋值语句(比如,beatles.members = [...])来创建关系:

# THIS WILL NOT WORK
>>> beatles.members.add(john)
# NEITHER WILL THIS
>>> beatles.members.create(name="George Harrison")
# AND NEITHER WILL THIS
>>> beatles.members = [john, paul, ringo, george]

为什么不能这样做? 这是因为你不能只创建 Person和 Group之间的关联关系,你还要指定 Membership模型中所需要的所有信息;而简单的add、create 和赋值语句是做不到这一点的。所以它们不能在使用中介模型的多对多关系中使用。此时,唯一的办法就是创建中介模型的实例。

remove()方法被禁用也是出于同样的原因。但是clear() 方法却是可用的。它可以清空某个实例所有的多对多关系:

>>> # Beatles have broken up
>>> beatles.members.clear()
>>> # Note that this deletes the intermediate model instances
>>> Membership.objects.all()

数据库表反向生成模型类

我们在展示django ORM反向生成之前,我们先说一下怎么样正向生成代码。


正向生成,指的是先创建model.py文件,然后通过django内置的编译器,在数据库如mysql中创建出符合model.py的表。


反向生成,指的是先在数据库中create table,然后通过django内置的编译器,生成model代码。


python manage.py inspectdb > models文件名

查询优化

(1)select_related()

对于一对一字段(OneToOneField)和外键字段(ForeignKey),可以使用select_related 来对QuerySet进行优化。


select_related 返回一个QuerySet,当执行它的查询时它沿着外键关系查询关联的对象的数据。它会生成一个复杂的查询并引起性能的损耗,但是在以后使用外键关系时将不需要数据库查询。


简单说,在对QuerySet使用select_related()函数后,Django会获取相应外键对应的对象,从而在之后需要的时候不必再查询数据库了。


下面的例子解释了普通查询和select_related() 查询的区别。

查询id=2的的书籍的出版社名称,下面是一个标准的查询:

# Hits the database.
book= models.Book.objects.get(nid=2)
# Hits the database again to get the related Blog object.
print(book.publish.name)

如果我们使用select_related()函数:

books=models.Book.objects.select_related("publish").all()
for book in books:
     #  Doesn't hit the database, because book.publish
     #  has been prepopulated in the previous query.
     print(book.publish.name)

多外键查询

这是针对publish的外键查询,如果是另外一个外键呢?让我们一起看下:

book=models.Book.objects.select_related("publish").get(nid=1)
print(book.authors.all())

观察logging结果,发现依然需要查询两次,所以需要改为:

book=models.Book.objects.select_related("publish","").get(nid=1)
print(book.publish)

或者:

book=models.Article.objects
             .select_related("publish")
             .select_related("")
             .get(nid=1)  # django 1.7 支持链式操作
print(book.publish)

(2)prefetch_related()

对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。


prefetch_related()和select_related()的设计目的很相似,都是为了减少SQL查询的数量,但是实现的方式不一样。后者是通过JOIN语句,在SQL查询内解决问题。但是对于多对多关系,使用SQL语句解决就显得有些不太明智,因为JOIN得到的表将会很长,会导致SQL语句运行时间的增加和内存占用的增加。若有n个对象,每个对象的多对多字段对应Mi条,就会生成Σ(n)Mi 行的结果表。prefetch_related()的解决方法是,分别查询每个表,然后用Python处理他们之间的关系。

# 查询所有文章关联的所有标签
books=models.Book.objects.all()
for book in books:
    print(book.authors.all())  #4篇文章: hits database 5

改为prefetch_related:

# 查询所有文章关联的所有标签
books=models.Book.objects.prefetch_related("authors").all()
for book in books:
    print(book.authors.all())  #4篇文章: hits database 2

(3)extra

extra(select=None, where=None, params=None, 
      tables=None, order_by=None, select_params=None)

有些情况下,Django的查询语法难以简单的表达复杂的 WHERE 子句,对于这种情况, Django 提供了 extra() QuerySet修改机制 — 它能在 QuerySet生成的SQL从句中注入新子句


extra可以指定一个或多个 参数,例如 select, where or tables. 这些参数都不是必须的,但是你至少要使用一个!要注意这些额外的方式对不同的数据库引擎可能存在移植性问题.(因为你在显式的书写SQL语句),除非万不得已,尽量避免这样。


参数之select

The select 参数可以让你在 SELECT 从句中添加其他字段信息,它应该是一个字典,存放着属性名到 SQL 从句的映射。

queryResult=models.Article.objects.extra(select={'is_recent': "create_time > '2017-09-05'"})

结果集中每个 Entry 对象都有一个额外的属性is_recent, 它是一个布尔值,表示 Article对象的create_time 是否晚于2017-09-05.


参数之where / tables


您可以使用where定义显式SQL WHERE子句 - 也许执行非显式连接。您可以使用tables手动将表添加到SQL FROM子句。


where和tables都接受字符串列表。所有where参数均为“与”任何其他搜索条件。

举例来讲:

queryResult=models.Article.objects.extra(where=['nid in (3,4) OR title like "py%" ','nid>2'])

七、Ajax请求

客户端(浏览器)向服务端发起请求的形式:


地址栏:GET


超链接标签:GET


form表单:GET或POST


Ajax(重要):GET或POST或PUT或DELETE


AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML,现在更多使用json数据)。


AJAX的特点和优点:


异步


局部刷新


发送ajax有很多种方法,如用xhr(XMLHttpRequest),jQuery,axios(当前最火),fetch,这里主要讲Jquery(人人都会)。

Ajax请求案例

(1) 视图

# Create your views here.
def reg(request):
    return render(request, "reg.html")
def reg_user(request):
    data = {"msg": "", "state": "success"}
    user = request.POST.get("user")
    if user == "yuan":
        data["state"] = "error"
        data["msg"] = "该用户已存在!"
    return JsonResponse(data)

(2) 模板:reg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>用户名:<input type="text"><span class="error" style="color: red"></span></p>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<script>
    $(":text").blur(function () {
        // 发送Ajax请求
        $.ajax({
            url: "http://127.0.0.1:8008/reg_user/",
            type: "post",
            data: {
                user: $(":text").val(),
            },
            success: function (response) {
                console.log(response);
                $(".error").html(response.msg);
            }
        })
    })
</script>
</body>
</html>

(3) 流程图

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

(4) 常用参数

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

返回结果:

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

缓存问题:

可以自己写个例子试一下,如第一次发送请求返回字符串‘123’,这时你修改响应代码为‘456’再次发生ajax,不同浏览器返回结果不一样。

解决办法:

可以在每次请求后面加个时间戳意思一下,这样每次都不得不重新发请求了:

url: 'http://127.0.0.1:8000/students/xx?t='+Date.now(),

同源策略

同源策略和跨域

现在我们将reg.html单独放在客户端,用浏览器打开,再触发事件,会发现报错:

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

这是因为浏览器的同源策略导致的。


同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略基础之上的,浏览器只是针对同源策略的一种实现。


同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口相同。当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当浏览器的百度tab页执行一个脚本的时候会检查这个脚本是属于哪个页面的,即检查是否同源,只有和百度同源的脚本才会被执行。如果非同源,那么在请求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。


那么如何解决这种跨域问题呢,我们主要由三个思路:


jsonp


cors


前端代理


这里主要给大家介绍第二个:cors


CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。


整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。


因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。


所以,服务器方面只要添加一个响应头,同意跨域请求,浏览器也就不再拦截:

response = JsonResponse(data)
response["Access-Control-Allow-Origin"] = "*"

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

在抓包工具中在这里(翻译下英文):

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

cors

cors有两种请求:简单请求和非简单请求

只要同时满足以下两大条件,就属于简单的请求

(1) 请求方法是以下三种方法之一:

HEAD

GET

POST

(2)HTTP的头信息不超出以下几种字段:

Accept

Accept-Language

Content-Language

Last-Event-ID

Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

凡是不同时满足上面两个条件,就属于非简单请求。浏览器对这两种请求的处理,是不一样的。

简单请求:一次请求

非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。

- 请求方式:OPTIONS

- “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息

- 如何“预检”

    => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过

       Access-Control-Request-Method

    => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过

       Access-Control-Request-Headers

支持跨域,简单请求:


服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'

支持跨域复杂请求:


由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。

“预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method

“预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers

cors在Django中的实现:

在返回结果中加入允许信息(简单请求):

def test(request):
    import json
    obj=HttpResponse(json.dumps({'name':'yuan'}))
    # obj['Access-Control-Allow-Origin']='*'
    obj['Access-Control-Allow-Origin']='http://127.0.0.1:8004'
    return obj

放到中间件中处理复杂和简单请求:

from django.utils.deprecation import MiddlewareMixin
class MyCorsMiddle(MiddlewareMixin):
    def process_response(self, request, response):
        # 简单请求:
        # 允许http://127.0.0.1:8001域向我发请求
        # ret['Access-Control-Allow-Origin']='http://127.0.0.1:8001'
        # 允许所有人向我发请求
        response['Access-Control-Allow-Origin'] = '*'
        if request.method == 'OPTIONS':
            # 所有的头信息都允许
            response['Access-Control-Allow-Headers'] = '*'
        return response

在settings中配置即可,在中间件中的位置可以随意放置.

也可以通过第三方组件:pip install django-cors-headers

# (1)
pip install django-cors-headers
# (2)
 INSTALLED_APPS = (
 'corsheaders',
)
# (3)
 1 MIDDLEWARE = [
 2     'django.middleware.security.SecurityMiddleware',
 3     'django.contrib.sessions.middleware.SessionMiddleware',
 4     'django.middleware.csrf.CsrfViewMiddleware',
 5     'django.contrib.auth.middleware.AuthenticationMiddleware',
 6     'django.contrib.messages.middleware.MessageMiddleware',
 7     'django.middleware.clickjacking.XFrameOptionsMiddleware',
 8     'corsheaders.middleware.CorsMiddleware',  # 按顺序
 9     'django.middleware.common.CommonMiddleware',  # 按顺序
10 ]
  # 配置白名单
 1 CORS_ALLOW_CREDENTIALS = True#允许携带cookie
 2 CORS_ORIGIN_ALLOW_ALL = True
 3 CORS_ORIGIN_WHITELIST = ( '*')#跨域增加忽略
 4 CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', )
 5 #允许的请求头
 6 CORS_ALLOW_HEADERS = (
 7     'XMLHttpRequest',
 8     'X_FILENAME',
 9     'accept-encoding',
10     'authorization',
11     'content-type',
12     'dnt',
13     'origin',
14     'user-agent',
15     'x-csrftoken',
16     'x-requested-with',
17     'Pragma',
18 )

前端项目设置请求头记得添加到CORS_ALLOW_HEADERS



相关文章
|
7天前
|
前端开发 数据库 数据安全/隐私保护
|
5月前
|
SQL 前端开发 JavaScript
Django零基础-快速了解基本框架笔记-附案例
Django零基础-快速了解基本框架笔记-附案例
62 0
|
5月前
|
存储 安全 数据库
关于“Python”Django 管理网站的核心知识点整理大全52
关于“Python”Django 管理网站的核心知识点整理大全52
36 0
|
5月前
|
JSON 前端开发 JavaScript
前端知识笔记(三十七)———Django与Ajax
前端知识笔记(三十七)———Django与Ajax
47 0
|
5月前
|
JSON 前端开发 JavaScript
前端知识笔记(二)———Django与Ajax
前端知识笔记(二)———Django与Ajax
56 0
|
存储 缓存 中间件
Django相关知识点回顾(二)
Django相关知识点回顾(二)
|
JSON 关系型数据库 数据库
|
Python
Django知识点-URL路由 name=
Django知识点-URL路由 name=
68 0
|
存储 Web App开发 前端开发
Django知识点笔记(4)
Django知识点笔记(4)
Django知识点笔记(4)
|
SQL 数据可视化 前端开发
Django知识点笔记(2)
Django知识点笔记(2)
Django知识点笔记(2)