ToB企服应用市场:ToB评测及商务社交产业平台

标题: Django具体笔记 [打印本页]

作者: 不到断气不罢休    时间: 2024-7-7 15:24
标题: Django具体笔记
django 学习

特点

URL 组成部分

URL: 同意资源定位符
一个URL由以下几部分组成
  1. scheme://host:port/path/?query-string=xxx#anchor
复制代码
注意:url中的所有字符串都是ASCII字符集,如果非ASCII字符,比如中文,浏览器会进行编码再进行传输
1. Django开篇

MVT 与 MVC
Django采用了MVT的设计模式,即模型(Model),视图(View)和模板(Template)
MVT模型的工作流程

2. 使用

安装django
pip  install  django
2.1 创建第一个项目

  1. django-admin startproject 项目名
复制代码
2.2 启动django项目

2.3 创建app
  1. python   manage.py   startapp   appname
复制代码
3. 路由控制器

在django中所有的路由最终都被保存到一个变量urlpatterns,urlpatterns必须声明在主应用下的urls.py总路由中,这些配置是由settings设置的。

在给urlpatterns路由列表添加路由的过程中,django提供了两个函数给开发者注册路由。
  1. from django.urls import path      # 字符串路由
  2. from django.urls import re_path   # 正则路由,会把url地址看成一个正则模式与客户端的请求url地址进行正则匹配
复制代码
4. 视图

django的视图有两种:分别是函数视图(FBV)和类视图(CBV)
5. 模板

忽略  (前后端分离项目  忽略)
3、静态文件
开发中在开启了debug模式时,django可以通过配置,答应用户通过对应的url地址访问django的静态文件。
setting.py,代码:
STATIC_ROOT = BASE_DIR / 'static'

STATIC_URL = '/static/'   # django模板中,可以引用{{STATIC_URL}}变量避免把路径写死。
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
os.path.join(BASE_DIR, 'static2'),
os.path.join(BASE_DIR, 'static3'),
]
从上往下依次查找
像前端背面写的这些路径
这里的/static/虽然写的时候就会提示static文件名,但实际上它的名字要和settings.py里的STATIC_URL一致。同样STATICFILES_DIRS对应的名字则为文件夹名。
6. 模型层 (ORM)

django中内嵌了ORM框架,不需要直接编写SQL语句进行数据库操作,二十通过定义模型类,操作模型类来完成数据库中表的增编削查和创建等操作。其内部是通过pymysql完成的,发送一些mysql语句

通过以下步骤来使用django的数据库操作
  1. 1. 配置数据库链接信息
  2. 2. 在model.py中定义模型类
  3. 3. 生成数据库迁移文件并执行迁移文件
  4. 4. 通过模型类对象提供的方法或属性完成数据表的增删改查操作
复制代码
6.1 配置数据库链接

在settings.py中保存了数据库的链接配置信息,Django默认初始化配置使用sqlite数据库。
6.2 定义模型类

接下来以学生管理为例进行演示。[系统大概3-4表,学生信息,课程信息,老师信息]
在models.py 文件中定义模型类。
  1. from django.db import models
  2. from datetime import datetime
  3. # 模型类必须要直接或者间接继承于 models.Model
  4. class BaseModel(models.Model):
  5. """公共模型[公共方法和公共字段]"""
  6. # created_time = models.IntegerField(default=0, verbose_name="创建时间")
  7. created_time = models.DateTimeField(auto_now_add=True, verbose_name="创建时间")
  8. # auto_now_add 当数据添加时设置当前时间为默认值
  9. # auto_now= 当数据添加/更新时, 设置当前时间为默认值
  10. updated_time = models.DateTimeField(auto_now=True)
  11. class Meta(object):
  12.         abstract = True # 设置当前模型为抽象模型, 当系统运行时, 不会认为这是一个数据表对应的模型.
  13. class Student(BaseModel):
  14. """Student模型类"""
  15. #1. 字段[数据库表字段对应]
  16. SEX_CHOICES = (
  17.         (0,"女"),
  18.         (1,"男"),
  19.         (2,"保密"),
  20. )
  21. # 字段名 = models.数据类型(约束选项1,约束选项2, verbose_name="注释")
  22. # SQL: id bigint primary_key auto_increment not null comment="主键",
  23. # id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # django会自动在创建数据表的时候生成id主键/还设置了一个调用别名 pk
  24. # SQL: name varchar(20) not null comment="姓名"
  25. # SQL: key(name),
  26. name = models.CharField(max_length=20, db_index=True, verbose_name="姓名" )
  27. # SQL: age smallint not null comment="年龄"
  28. age = models.SmallIntegerField(verbose_name="年龄")
  29. # SQL: sex tinyint not null comment="性别"
  30. # sex = models.BooleanField(verbose_name="性别")
  31. sex = models.SmallIntegerField(choices=SEX_CHOICES, default=2)
  32. # SQL: class varchar(5) not null comment="班级"
  33. # SQL: key(class)
  34. classmate = models.CharField(db_column="class", max_length=5, db_index=True, verbose_name="班级")
  35. # SQL: description longtext default "" not null comment="个性签名"
  36. description = models.TextField(default="", verbose_name="个性签名")
  37. #2. 数据表结构信息
  38. class Meta:
  39.         db_table = 'tb_student'  # 指明数据库表名,如果没有指定表明,则默认为子应用目录名_模型名称,例如: users_student
  40.         verbose_name = '学生信息表'  # 在admin站点中显示的名称
  41.         verbose_name_plural = verbose_name  # 显示的复数名称
  42.      # 除此之外还可以设置联合索引 联合唯一索引等(注意可能会与unique=True发生冲突)
  43.       #3. 自定义数据库操作方法
  44.      def __str__(self):
  45.          """定义每个数据对象的显示信息"""
  46.          return "<User %s>" % self.name
复制代码
真正的项目,一些登岸注册等的功能,对于用户表,背面会讲到用户认证组件Auth模块,我们一般在models.py里自定义一个class User()继承自带的Auth表。
(1) 数据库表名
模型类如果未指明表名db_table,Django默认以 小写app应用名_小写模型类名 为数据库表名
可通过db_table 指明数据库表名。
(2) 关于主键
django会为表创建主动增长的主键列,每个模型只能有一个主键列。
如果使用选项设置某个字段的约束属性为主键列(primary_key)后,django不会再创建主动增长的主键列。
class Student(models.Model):
# django会主动在创建数据表的时候天生id主键/还设置了一个调用别名 pk
id = models.AutoField(primary_key=True, null=False, verbose_name="主键") # 设置主键
默认创建的主键列属性为id,可以使用pk代替,pk全拼为primary key。
(3) 属性命名限制
不能是python的保留关键字。
不答应使用连续的2个下划线,这是由django的查询方式决定的。__ 是关键字来的,不能使用!!!
定义属性时需要指定字段范例,通过字段范例的参数指定选项,语法如下:
属性名 = models.字段范例(约束选项, verbose_name="解释")
(4)字段范例
范例说明AutoField主动增长的IntegerField,通常不用指定,不指定时Django会主动创建属性名为id的主动增长属性BooleanField布尔字段,值为True或FalseNullBooleanField支持Null、True、False三种值CharField字符串,参数max_length表示最大字符个数,对应mysql中的varcharTextField大文本字段,一般大段文本(超过4000个字符)才使用。IntegerField整数DecimalField十进制浮点数, 参数max_digits表示总位数, 参数decimal_places表示小数位数,常用于表示分数和价格 Decimal(max_digits=7, decimal_places=2) ==> 99999.99~ 0.00FloatField浮点数DateField日期 参数auto_now表示每次保存对象时,主动设置该字段为当前时间。 参数auto_now_add表示当对象第一次被创建时主动设置当前。 参数auto_now_add和auto_now是相互排斥的,一起使用会发生错误。TimeField时间,参数同DateFieldDateTimeField日期时间,参数同DateFieldFileField上传文件字段,django在文件字段中内置了文件上传保存类, django可以通过模型的字段存储主动保存上传文件, 但是, 在数据库中本质上保存的仅仅是文件在项目中的存储路径!!ImageField继承于FileField,对上传的内容进行校验,确保是有效的图片注意:手机号不要用IntegerField,超过范围了
(5)约束选项
选项说明null如果为True,表示答应为空,默认值是False。相当于python的Noneblank如果为True,则该字段答应为空白,默认值是False。 相当于python的空字符串,“”db_column字段的名称,如果未指定,则使用属性的名称。(展示名称)db_index若值为True, 则在表中会为此字段创建索引,默认值是False。 相当于SQL语句中的keydefault默认值,当不填写数据时,使用该选项的值作为数据的默认值。primary_key如果为True,则该字段会成为模型的主键,默认值是False,一般不用设置,系统默认设置。unique如果为True,则该字段在表中必须有唯一值,默认值是False。相当于SQL语句中的uniqueverbose_name admin里体现的名字
注意:null是数据库范畴的概念,blank是表单验证范畴的(同理default也是)。更改一些class属性参数的时候可以不用重新数据库迁移 ,而修改数据库结构的需要重新迁移。
数据可范畴出错会发生报错,用户输入范畴不符合规定会提示不能为空,大概自增、默认值。
后加东西要是设置默认值
(6)外键
在设置外键时,需要通过on_delete选项指明主表删除数据时,对于外键引用表数据怎样处理,在django.db.models中包含了可选常量:
CASCADE 级联,删除主表数据时连通一起删除外键表中数据
PROTECT 保护,通过抛出ProtectedError异常,来制止删除主表中被外键应用的数据
SET_NULL 设置为NULL,仅在该字段null=True答应为null时可用
SET_DEFAULT 设置为默认值,仅在该字段设置了默认值时可用
SET() 设置为特定值大概调用特定方法,例如:
  1. from django.conf import settings
  2. from django.contrib.auth import get_user_model
  3. from django.db import models
  4. def get_sentinel_user():
  5. return get_user_model().objects.get_or_create(username='deleted')[0]
  6. class UserModel(models.Model):
  7. user = models.ForeignKey(
  8.      settings.AUTH_USER_MODEL,
  9.      on_delete=models.SET(get_sentinel_user),
  10. )
复制代码
商品分类表
idcategory1蔬菜2电脑商品信息表
idgoods_namecid1冬瓜12华为笔记本23茄子1
当模型字段的on_delete=CASCADE, 删除蔬菜(id=1),则在外键cid=1的商品id1和3就被删除。
当模型字段的on_delete=PROTECT,删除蔬菜,mysql主动查抄商品信息表,有没有cid=1的记录,有则提示必须先移除掉商品信息表中,id=1的所有记录以后才能删除蔬菜。
当模型字段的on_delete=SET_NULL,删除蔬菜以后,对应商品信息表,cid=1的数据的cid全部被改成cid=null
当模型字段的on_delete=SET_DEFAULT,删除蔬菜以后,对应商品信息表,cid=1的数据记录的cid被被设置默认值。
6.2 数据库迁移

将模型类定义表架构的代码转换成SQL同步到数据库中,这个过程就是数据迁移。django中的数据迁移,就是一个类,这个类提供了一系列的终端命令,帮我们完成数据迁移的工作。
(1)天生迁移文件
所谓的迁移文件, 是类似模型类的迁移类,主要是描述了数据表结构的类文件.
python manage.py makemigrations
(2)同步到数据库中
python manage.py migrate
增补:在django内部提供了一系列的功能,这些功能也会使用到数据库,以是在项目搭建以后第一次数据迁移的时候,会看到django项目中其他的数据表被创建了。此中就有一个django内置的admin站点管理。(这个背面会写)
admin站点默认是开启状态的,我们可以通过http://127.0.0.1:8000/admin
这个站点必须有个管理员账号登录,以是我们可以在第一次数据迁移,有了数据表以后,就可以通过以下终端命令来创建一个超级管理员账号。
python manage.py createsuperuser
(背景超级管理员看到的表需要注册,且一些自增字段不会体现)
这里我注册用户名root 密码root123 进入admin

这是因为我之前用auth组件设置过一个普通用户,浏览器中还保存着sessionid,这个普通用户显然不是管理员,无权登录。

我们进入自己的root账户

在admin.py里注册表


记得在models.py里加上def__str__ 如许背景也就能体现相应的中文表示的对象了。
6.3 数据库根本操作

6.3.1 脚本orm操作数据库

注意导入次序,不然会报错
(父级models类里有__str__和__repr__方法,也可以把__repr__重写)
  1. import os
  2. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'homework06.settings')
  3. import django
  4. django.setup()
  5. from students.models import *
  6. ret = Course.objects.all()# <QuerySet [<Course: 课程1>, <Course: 课程2>, <Course: 课程3>]>
  7. ret = Course.objects.get(pk=1) # 课程1
  8. ret = Course.objects.filter(pk=1)# <QuerySet [<Course: 课程1>]>
复制代码
6.3.2  基础查询

ORM中针对查询结果的限制,提供了一个查询集[QuerySet].这个QuerySet,是ORM中针对查询结果进行保存数据的一个范例,我们可以通过相识这个QuerySet进行使用,达到查询优化,大概限制查询结果数目的作用。
背面会具体讲QuerySet,orm进阶及优化搜刮,到这里要先相识一些根本操作。
  1. def index(request):
  2.     print(Students.objects, type(Students.objects))#app01.Students.objects <class 'django.db.models.manager.Manager'>
  3.     return HttpResponse('ok')
复制代码
小总结
6.3.3 模糊查询

6.3.4 进阶查询

6.3.5 修改记录

6.3.6删除记录

删除有两种方法
(1)模型类对象.delete
  1. student = Student.objects.get(id=13)
  2. student.delete()
复制代码
(2)模型类.objects.filter().delete()
  1. Student.objects.filter(id=14).delete()
复制代码
代码:
  1. # 1. 先查询到数据模型对象。通过模型对象进行删除
  2. student = Student.objects.filter(pk=13).first()
  3. student.delete()
  4. # 2. 直接删除
  5. ret = Student.objects.filter(pk=100).delete()
  6. print(ret)
  7. # 务必写上条件,否则变成了清空表了。ret = Student.objects.filter().delete()
复制代码
6.4 创建关联模型

实例:我们来假定下面这些概念,字段和关系
  1. from django.db import models
  2. # Create your models here.
  3. class Clas(models.Model):
  4.     name = models.CharField(max_length=32, unique=True, verbose_name="班级名称")
  5.     class Meta:
  6.         db_table = "db_class"
  7. class Course(models.Model):
  8.     name = models.CharField(max_length=32, unique=True, verbose_name="课程名称")
  9.     class Meta:
  10.         db_table = "db_course"
  11. class Student(models.Model):
  12.     sex_choices = (
  13.         (0, "女"),
  14.         (1, "男"),
  15.         (2, "保密"),
  16.     )
  17.     name = models.CharField(max_length=32, unique=True, verbose_name="姓名")
  18.     age = models.SmallIntegerField(verbose_name="年龄", default=18)  # 年龄
  19.     sex = models.SmallIntegerField(choices=sex_choices)
  20.     birthday = models.DateField()
  21.     # 一对多
  22.     # on_delete= 关联关系的设置
  23.     # models.CASCADE    删除主键以后, 对应的外键所在数据也被删除
  24.     # models.DO_NOTHING 删除主键以后, 对应的外键不做任何修改
  25.     # 反向查找字段 related_name
  26.     clas = models.ForeignKey("Clas", on_delete=models.CASCADE)
  27.     # 多对多
  28.     # 建立多对多的关系,ManyToManyField可以建在两个模型中的任意一个,自动创建第三张表
  29.     courses = models.ManyToManyField("Course", db_table="db_student2course")
  30.     # 一对一,使用同一对多
  31.     stu_detail = models.OneToOneField("StudentDetail", on_delete=models.CASCADE)
  32.     class Meta:
  33.         db_table = "db_student"
  34.     def __str__(self):
  35.         return self.name
  36. class StudentDetail(models.Model):
  37.     tel = models.CharField(max_length=32)
  38.     email = models.EmailField()
  39.     description = models.TextField(null=True, verbose_name="个性签名")
  40.     class Meta:
  41.         db_table = "db_student_detail"
复制代码
6.4.1 关联添加

6.4.2 关联查询

7.ORM进阶

7.1 queryset特性

7.2  中介模型

处理类似搭配 pizza 和 topping 如许简单的多对多关系时,使用标准的ManyToManyField 就可以了。但是,有时你大概需要关联数据到两个模型之间的关系上。
例如,有如许一个应用,它记灌音乐家所属的音乐小组。我们可以用一个ManyToManyField 表示小组和成员之间的多对多关系。但是,有时你大概想知道更多成员关系的细节,比如成员是何时加入小组的。
对于这些情况,Django 答应你指定一个中介模型来定义多对多关系。 你可以将其他字段放在中介模型里面。源模型的ManyToManyField 字段将使用through 参数指向中介模型。对于上面的音乐小组的例子,代码如下:
  1. from django.db import models
  2. class Person(models.Model):
  3.     name = models.CharField(max_length=128)
  4.     def __str__(self):              # __unicode__ on Python 2
  5.         return self.name
  6. class Group(models.Model):
  7.     name = models.CharField(max_length=128)
  8.     members = models.ManyToManyField(Person, through='Membership')
  9.     def __str__(self):              # __unicode__ on Python 2
  10.         return self.name
  11. class Membership(models.Model):
  12.     person = models.ForeignKey(Person)
  13.     group = models.ForeignKey(Group)
  14.     date_joined = models.DateField()
  15.     invite_reason = models.CharField(max_length=64)
复制代码
既然你已经设置好ManyToManyField 来使用中介模型(在这个例子中就是Membership),接下来你要开始创建多对多关系。你要做的就是创建中介模型的实例:
  1. >>> ringo = Person.objects.create(name="Ringo Starr")
  2. >>> paul = Person.objects.create(name="Paul McCartney")
  3. >>> beatles = Group.objects.create(name="The Beatles")
  4. >>> m1 = Membership(person=ringo, group=beatles,
  5. ...     date_joined=date(1962, 8, 16),
  6. ...     invite_reason="Needed a new drummer.")
  7. >>> m1.save()
  8. >>> beatles.members.all()
  9. [<Person: Ringo Starr>]
  10. >>> ringo.group_set.all()
  11. [<Group: The Beatles>]
  12. >>> m2 = Membership.objects.create(person=paul, group=beatles,
  13. ...     date_joined=date(1960, 8, 1),
  14. ...     invite_reason="Wanted to form a band.")
  15. >>> beatles.members.all()
  16. [<Person: Ringo Starr>, <Person: Paul McCartney>]
复制代码
与普通的多对多字段不同,你不能使用add、 create和赋值语句(比如,beatles.members = [...])来创建关系:
  1. # THIS WILL NOT WORK
  2. >>> beatles.members.add(john)
  3. # NEITHER WILL THIS
  4. >>> beatles.members.create(name="George Harrison")
  5. # AND NEITHER WILL THIS
  6. >>> beatles.members = [john, paul, ringo, george]
复制代码
为什么不能如许做? 这是因为你不能只创建 Person和 Group之间的关联关系,你还要指定 Membership模型中所需要的所有信息;而简单的add、create 和赋值语句是做不到这一点的。以是它们不能在使用中介模型的多对多关系中使用。此时,唯一的办法就是创建中介模型的实例。
remove()方法被禁用也是出于同样的缘故原由。但是clear() 方法却是可用的。它可以清空某个实例所有的多对多关系:
  1. >>> # Beatles have broken up
  2. >>> beatles.members.clear()
  3. >>> # Note that this deletes the intermediate model instances
  4. >>> Membership.objects.all()
复制代码
7.3 数据库表反向天生模型类

众所周知,Django较为适合原生开发,即通过该框架搭建一个全新的项目,通过在修改models.py来创建新的数据库表。但是往往有时候,我们需要利用到之前的已经设计好的数据库,数据库中提供了设计好的多种表单。那么这时如果我们再通过models.py再来设计就会浪费很多的时间。所幸Django为我们提供了inspecdb的方法。他的作用即使根据已经存在对的mysql数据库表来反向映射结构到models.py中.
我们在展示django ORM反向天生之前,我们先说一下怎么样正向天生代码。
正向天生,指的是先创建model.py文件,然后通过django内置的编译器,在数据库如mysql中创建出符合model.py的表。
反向天生,指的是先在数据库中create table,然后通过django内置的编译器,天生model代码。
  1. python manage.py inspectdb > models文件名
复制代码
7.4 查询优化

8. Ajax哀求

客户端(浏览器)向服务端发起哀求的形式:
AJAX(Asynchronous Javascript And XML)翻译成中文就是“异步Javascript和XML”。即使用Javascript语言与服务器进行异步交互,传输的数据为XML(当然,传输的数据不只是XML,现在更多使用json数据)。
AJAX的特点和优点:
发送ajax有很多种方法,如用xhr(XMLHttpRequest),jQuery,axios(当前最火),fetch,这里主要讲Jquery(人人都会)。
8.1 Ajax哀求案例

8.1.1 视图
  1. # Create your views here.
  2. def reg(request):
  3.     return render(request, "reg.html")
  4. def reg_user(request):
  5.     data = {"msg": "", "state": "success"}
  6.     user = request.POST.get("user")
  7.     if user == "yuan":
  8.         data["state"] = "error"
  9.         data["msg"] = "该用户已存在!"
  10.     return JsonResponse(data)
复制代码
模板:reg.html
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>Title</title>
  6. </head>
  7. <body>
  8. <p>用户名:<input type="text"></p >
  9. </body>
  10. </html>
复制代码
流程图

常用参数

返回结果

缓存题目:
对于一些浏览器如IE(新版本不知道还会不会有),内部的缓存会影响到ajax的结果,但是像谷歌等的不会。意思是当你第二次发起ajax哀求时,浏览器不会向服务器发起,而走的是缓存,如许你只会体现第一次的数据。
可以自己写个例子试一下,如第一次发送哀求返回字符串‘123’,这时你修改相应代码为‘456’再次发生ajax,不同浏览器返回结果不一样。
解决办法:
可以在每次哀求背面加个时间戳意思一下,如许每次都不得不重新发哀求了:
url: 'http://127.0.0.1:8000/students/xx?t='+Date.now(),
8.1.2同源策略

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

这是因为浏览器的同源策略导致的。
同源策略(Same origin policy)是一种约定,它是浏览器最核心也最根本的安全功能,如果缺少了同源策略,则浏览器的正常功能可
同源策略,它是由Netscape提出的一个闻名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。所谓同源是指,域名,协议,端口雷同。当一个浏览器的两个tab页中分别打开来 百度和谷歌的页面当浏览器的百度tab页执行一个脚本的时候会查抄这个脚本是属于哪个页面的,即查抄是否同源,只有和百度同源的脚本才会被执行。如果非同源,那么在哀求数据时,浏览器会在控制台中报一个异常,提示拒绝访问。
那么怎样解决这种跨域题目呢,我们主要由三个思绪:
这里主要给大家介绍第二个:cors
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。
整个CORS通信过程,都是浏览器主动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX哀求跨源,就会主动添加一些附加的头信息,有时还会多出一次附加的哀求,但用户不会有感觉。
因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
以是,服务器方面只要添加一个相应头,同意跨域哀求,浏览器也就不再拦截:
response = JsonResponse(data)
response["Access-Control-Allow-Origin"] = "*"

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

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
凡是不同时满足上面两个条件,就属于非简单哀求。浏览器对这两种哀求的处理,是不一样的。
简单哀求:一次哀求
非简单哀求:两次哀求,在发送数据之前会先发一次哀求用于做“预检”,只有“预检”通过后才再发送一次哀求用于数据传输。
  1. - 请求方式:OPTIONS
  2. - “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
  3. - 如何“预检”
  4.      => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
  5.         Access-Control-Request-Method
  6.      => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
  7.         Access-Control-Request-Headers
复制代码
支持跨域,简单哀求:
  1. 服务器设置响应头:Access-Control-Allow-Origin = '域名' 或 '*'
复制代码
支持跨域复杂哀求:
  1. 由于复杂请求时,首先会发送“预检”请求,如果“预检”成功,则发送真实数据。
  2. “预检”请求时,允许请求方式则需服务器设置响应头:Access-Control-Request-Method
  3. “预检”请求时,允许请求头则需服务器设置响应头:Access-Control-Request-Headers
复制代码
cors在Django中的实现:
在返回结果中加入答应信息(简单哀求):
  1. def test(request):
  2.     import json
  3.     obj=HttpResponse(json.dumps({'name':'yuan'}))
  4.     # obj['Access-Control-Allow-Origin']='*'
  5.     obj['Access-Control-Allow-Origin']='http://127.0.0.1:8004'
  6.     return obj
复制代码
放到中间件中处理复杂和简单哀求:
  1. from django.utils.deprecation import MiddlewareMixin
  2. class MyCorsMiddle(MiddlewareMixin):
  3.     def process_response(self, request, response):
  4.         # 简单请求:
  5.         # 允许http://127.0.0.1:8001域向我发请求
  6.         # ret['Access-Control-Allow-Origin']='http://127.0.0.1:8001'
  7.         # 允许所有人向我发请求
  8.         response['Access-Control-Allow-Origin'] = '*'
  9.         if request.method == 'OPTIONS':
  10.             # 所有的头信息都允许
  11.             response['Access-Control-Allow-Headers'] = '*'
  12.         return response
复制代码
在settings中配置即可,在中间件中的位置可以随意放置.
也可以通过第三方组件:pip install django-cors-headers
  1. # (1)
  2. pip install django-cors-headers
  3. # (2)
  4. INSTALLED_APPS = (
  5. 'corsheaders',
  6. )
  7.    
  8. # (3)
  9. MIDDLEWARE = [
  10.     'django.middleware.security.SecurityMiddleware',
  11.     'django.contrib.sessions.middleware.SessionMiddleware',
  12.      'django.middleware.csrf.CsrfViewMiddleware',
  13.     'django.contrib.auth.middleware.AuthenticationMiddleware',
  14.     'django.contrib.messages.middleware.MessageMiddleware',
  15.     'django.middleware.clickjacking.XFrameOptionsMiddleware',
  16.     'corsheaders.middleware.CorsMiddleware',  # 按顺序
  17.     'django.middleware.common.CommonMiddleware',  # 按顺序
  18. ]
  19. # 配置白名单
  20. 1 CORS_ALLOW_CREDENTIALS = True#允许携带cookie
  21. 2 CORS_ORIGIN_ALLOW_ALL = True
  22. 3 CORS_ORIGIN_WHITELIST = ( '*')#跨域增加忽略
  23. 4 CORS_ALLOW_METHODS = ( 'DELETE', 'GET', 'OPTIONS', 'PATCH', 'POST', 'PUT', 'VIEW', )
  24. #允许的请求头
  25. CORS_ALLOW_HEADERS = (
  26.      'XMLHttpRequest',
  27.      'X_FILENAME',
  28.      'accept-encoding',
  29.      'authorization',
  30.      'content-type',
  31.      'dnt',
  32.      'origin',
  33.      'user-agent',
  34.      'x-csrftoken',
  35.      'x-requested-with',
  36.      'Pragma',
  37. )
复制代码
9. 中间件

介于request与response处理之间的一道处理过程,相对比较轻量级,而且在全局上改变Django的输入与输出。因为改变的是全局,以是审慎使用。
如果你想修改哀求,例如被传送到view中的HttpRequest对象。大概你想修改view放回的HttpResponse对象,这些都可以通过中间件实现。
django框架内部声明了很多的中间件,这些中间件有着各种各种的用途,有些没有被使用,有些被默认开启使用了。而被开启使用的中间件,都是在settngs.py的MIDDLEWARE中注册使用的。
Django默认的Middleware:
  1. MIDDLEWARE = [
  2.     'django.middleware.security.SecurityMiddleware',
  3.     'django.contrib.sessions.middleware.SessionMiddleware',
  4.     'django.middleware.common.CommonMiddleware',
  5.     'django.middleware.csrf.CsrfViewMiddleware',
  6.     'django.contrib.auth.middleware.AuthenticationMiddleware',
  7.     'django.contrib.messages.middleware.MessageMiddleware',
  8.     'django.middleware.clickjacking.XFrameOptionsMiddleware',
  9. ]
复制代码
9.1 自定义中间件


9.2 中间件应用

9.3 Cookie与Session


第一次访问设置了cookie键值返回给浏览器,浏览器进行了保存,下次访问时会带着这组键值(cookie)去访问浏览器。信息保存在浏览器并不怎么安全。
cookie语法
  1. # (1) 设置cookie:
  2. res = HttpResponse(...) 或 rep = render(request, ...) 或 rep = redirect()
  3. res.set_cookie(key,value,max_age...)
  4. res.set_signed_cookie(key,value,salt='加密盐',...) 
  5. # (2) 获取cookie:
  6. request.COOKIES     request.get_signed_cookie('xx',salt='xxxxxx')  
  7. # (3) 删除cookie
  8. response.delete_cookie("cookie_key",path="/",domain=name)
复制代码
当我们访问不设置cookie的视图函数(含有csrf是因为我没有将中间件解释掉,背面会单独讲csrf)
  1. # 1、设置Sessions值
  2.        request.session['session_name'] ="admin"
  3. # 2、获取Sessions值
  4.        session_name = request.session["session_name"]
  5. # 3、删除Sessions值
  6.        del request.session["session_name"]
  7. # 4、flush()
  8.   # 删除当前的会话数据并删除会话的Cookie。这用于确保前面的会话数据不可以再次被用户的浏览器访问
复制代码
  1.   def s_login(request):
  2.       if request.method == "GET":
  3.           return render(request, "login.html")
  4.       else:
  5.           user = request.POST.get("user")
  6.           pwd = request.POST.get("pwd")
  7.           try:
  8.               # user_obj = User.objects.get(user=user,pwd=pwd)
  9.               # 写session
  10.               # request.session["is_login"] = True
  11.               # request.session["username"] = user_obj.user
  12.               return redirect("/s_index/")
  13.           except:
  14.               return redirect("/s_login/")
  15.   
  16.             
  17.   def s_index(request):
  18.       # 读session
  19.       is_login = request.session.get("is_login")
  20.       if is_login:
  21.           username = request.session.get("username")
  22.           return render(request, "index.html", {"user": username})
  23.       else:
  24.           return redirect("/s_login/")
  25.   '''
  26.   shop.html:
  27.   <p>
  28.   客户端最后一次访问时间:{{ last_time|default:"第一次访问" }}
  29.   </p >
  30.   <h3>商品页面</h3>
  31.   '''
  32.   
  33.   def shop(request):
  34.       last_time = request.session.get("last_time")
  35.       now = datetime.datetime.now().strftime("%Y-%m-%d %X")
  36.       request.session["last_time"] = now
  37.       return render(request, "shop.html", {"last_time": last_time})
  38.   
  39.   
  40.   def s_logout(request):
  41.       # request.session.flush()
  42.       del request.session["username"]
  43.       del request.session["is_login"]
  44.       return redirect("/s_login/")
  45.   
复制代码

session 在服务器端,cookie 在客户端(浏览器)
session 默认被存在在服务器的一个文件里(不是内存)
session 的运行依赖 session id,而 session id 是存在 cookie 中的.
session 可以放在 文件、数据库、或内存中都可以。
用户验证这种场所一般会用 session
Session配置
Django默认支持Session,而且默认是将Session数据存储在数据库中,即:django_session 表中。
  1. # 配置 settings.py
  2.   SESSION_ENGINE = 'django.contrib.sessions.backends.db'   # 引擎(默认)
  3.   
  4.   
  5.   SESSION_COOKIE_NAME = "sessionid"               # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
  6.   SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
  7.   SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
  8.   SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
  9.   SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
  10.   SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
  11.   SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
  12.   SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)
复制代码
9.4  用户认证组件

Django默认已经提供了认证系统Auth模块,我们认证的时候,会使用auth模块里面给我们提供的表。(基于session进一步封装,帮助我们登录认证)认证系统包含:


views.py
  1. def index(request):
  2.     # request.user:当前登录对象
  3.     '''
  4.         1. 在AuthenticationMiddleware中间件的process_request方法中取user_id
  5.             user_id = request.session.get('user_id')
  6.         2.  导入User表 from django.contrib.auth.models import User
  7.             user = User.object.get(pk=user_id)
  8.             if user:
  9.                 request.user = user
  10.             else:
  11.                 # 取不到返回一个匿名用户 所有属性都为零值(空或者None)
  12.                 request.user = AnonymousUser()
  13.         因为是在中间件中完成因此在任何视图函数中都可以使用request.user
  14.         只要完成了登录即auth.login(request, user) request.user就是登录对象
  15.         如果没有登录过或者失败,那就是匿名对象
  16.     '''
  17.     if request.user.id:  # 随便取request.user.name
  18.         return HttpResponse('登陆成功')
  19.     else:
  20.         return HttpResponse('请先去登录')
  21. def login(request):
  22.     if request.method == 'GET':
  23.         return render(request, 'login.html')
  24.     else:
  25.         account = request.POST.get('account')
  26.         pwd = request.POST.get('pwd')
  27.         # 找到返回对象 没找到返回None
  28.         user = auth.authenticate(username=account, password=pwd)
  29.         if user:
  30.             # request.session['user_id'] = user.pk
  31.             auth.login(request, user)  # 登陆成功后如果是第一次访问session表里的session就新增了
  32.             return redirect(reverse('students:index'))
  33.         else:
  34.             return HttpResponse('登陆失败')
  35. def logout(request):
  36.     # request.session.flush()
  37.     auth.logout(request)
  38.     return redirect(reverse('students:login'))
复制代码
登录前

登录中

登岸后(记得注掉csrf中间件大概form表单里加上{%csrf_token%}
  1. session表
  2. ![](https://img2024.cnblogs.com/blog/3088220/202407/3088220-20240707163828759-606627540.png)
复制代码
9.5 Django分页器

批量插入数据

from django.core.paginator import Paginator
9.6 FBV与CBV

1 FBV :function based view
2 BCV:class based view
9.6.1 前后段分离模式

在开发Web应用中,有两种应用模式:
​    1.前后端不分离[客户端看到的内容和所有界面效果都是由服务端提供出来的。

2.前后端分离(把前端的界面效果(html,css,js分离到另一个服务端,python服务端只需要返回数据即可)

前端形成一个独立的网站,服务端构成一个独立的网站
9.6.2 api接口

应用程序编程接口(Application Programming Interface,API接口),就是应用程序对外提供了一个操作数据的入口,这个入口可以是一个函数或类方法,也可以是一个url地址大概一个网络地址。当客户端调用这个入口,应用程序则会执行对应代码操作,给客户端完成相对应的功能。
当然,api接口在工作中是比较常见的开发内容,有时候,我们会调用其他人编写的api接口,有时候,我们也需要提供api接口给其他人操作。由此就会带来一个题目,api接口往往都是一个函数、类方法、大概url或其他网络地址,不绝是哪一种,当api接口编写过程中,我们都要思量一个题目就是这个接口应该怎么编写?接口怎么写的更加轻易维护和清楚,这就需要大家在调用大概编写api接口的时候要有一个明确的编写规范!!!
为了在团队内部形成共识、防止个人习惯差异引起的混乱,我们都需要找到一种大家都觉得很好的接口实现规范,而且这种规范可以或许让后端写的接口,用途一览无余,淘汰客户端和服务端两边之间的合作成本。
目前市面上大部分公司开发人员使用的接口实现规范主要有:restful、RPC。
RPC( Remote Procedure Call ): 翻译成中文:远程过程调用[远程服务调用]. 从字面上理解就是访问/调用远程服务端提供的api接口。这种接口一般以服务大概过程式代码提供。
rpc接口多了,对应函数名和参数就多了,前端在哀求api接口时难找.对于年代久远的rpc服务端的代码也轻易出现重复的接口
restful: 翻译成中文: 资源状态转换.(表征性状态转移)
也就是说,我们仅需要通过url地址上的资源名称结合HTTP哀求动作,就可以说明当前api接口的功能是什么了。restful是以资源为主的api接口规范,体现在地址上就是资源就是以名词表达。rpc则以动作为主的api接口规范,体现在接口名称上往往附带操作数据的动作。
9.6.3 RESTful API规范

REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中。
RESTful是一种专门为Web 开发而定义API接口的设计风格,尤其适用于前后端分离的应用模式中。
这种风格的理念认为后端开发任务就是提供数据的,对外提供的是数据资源的访问接口,以是在定义接口时,客户端访问的URL路径就表示这种要操作的数据资源。
而对于数据资源分别使用POST、DELETE、GET、UPDATE等哀求动作来表达对数据的增删查改。
GET/students获取所有学生哀求方法哀求地址后端操作POST/students增加学生GET/students/获取编号为pk的学生PUT/students/修改编号为pk的学生DELETE/students/删除编号为pk的学生restful规范是一种通用的规范,不限制语言和开发框架的使用。事实上,我们可以使用任何一门语言,任何一个框架都可以实现符合restful规范的API接口。
参考文档:RESTful 架构详解 | 菜鸟教程
接口实现过程中,会存在幂等性。所谓幂等性是指代客户端发起多次同样哀求时,是否对于服务端里面的资源产生不同结果。如果多次哀求,服务端结果还是一样,则属于幂等接口,如果多次哀求,服务端产生结果是不一样的,则属于非幂等接口。
哀求方式是否幂等是否安全GET幂等安全POST不幂等不安全PUT/PATCH幂等不安全DELETE幂等不安全9.6.4 CBV使用

之前我们用的视图函数叫FBV(也就是函数型视图函数),这里我们来试试CBV(类视图函数)的写法。类视图函数可以让代码看起来更简洁,用起来更方便。
  1. # FBV
  2. # def index(request):
  3. #     if request.method == "GET":
  4. #
  5. #         return HttpResponse("GET")
  6. #     elif request.method == "POST":
  7. #
  8. #         return HttpResponse("POST")
  9. #
  10. #     elif request.method == "DELETE":
  11. #         return HttpResponse("DELETE")
  12. # CBV模式: 基于restful开发
  13. class IndexView(View):
  14.     def get(self, request):
  15.         return HttpResponse("CBV GET")
  16.     def post(self, request):
  17.         return HttpResponse("CBV POST")
  18. class BookView(View):
  19.     def get(self, request):
  20.         # 获取数据
  21.         book_list = Book.objects.all()
  22.         # 序列化:json
  23.         data_json = serializers.serialize("json", book_list)
  24.         return HttpResponse(data_json, content_type="json")
  25. # FBV模式
  26. # path('index/', views.index),
  27. # CBV模式
  28. path("index/",views.IndexView.as_view()),
  29. path("books/",views.BookView.as_view())
复制代码
9.6.5 csrftoken(跨站哀求伪造)

CSRF(Cross-Site Request Forgery,跨站点伪造哀求)是一种网络攻击方式,该攻击可以在受害者毫不知情的情况下以受害者名义伪造哀求发送给受攻击站点,从而在未授权的情况下执行在权限保护之下的操作,具有很大的危害性。具体来讲,可以如许理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意哀求,对服务器来说这个哀求是完全合法的,但是却完成了攻击者所期望的一个操作,比如以你的名义发送邮件、发消息,盗取你的账号,添加系统管理员,乃至于购买商品、虚拟货币转账等。
  1. -- 企业邮箱
  2. -- pornhub.com
复制代码
参考文章
token其实就是一个令牌,用于用户验证的,token的诞生离不开CSRF。正是由于上面的Cookie/Session的状态保持方式会出现CSRF,以是才有了token。
根本使用
一、form表单提交
在html页面form表单中直接添加{% csrf_token%}
多了句
  1. [/code]64位随机字符串,前32是salt后32是token
  2. 如许提交post哀求时会多一组键值对,执行到 'django.middleware.csrf.CsrfViewMiddleware'中间件的时候内部会进行校验。浏览器哀求携带cookie里的token和哀求头/体里的token进行比对(两者不一样)。
  3. 下同
  4. 二、ajax提交
  5. 也要导入 {% csrf_token %} 不然没有[name="csrfmiddlewaretoken"]
  6. 方式1:放在哀求数据中。
  7. [code]$.ajax({
  8.   url: '/csrf_test/',
  9.   method: 'post',
  10.   data: {'name': $('[name="name"]').val(),
  11.          'password': $('[name="password"]').val(),
  12.          'csrfmiddlewaretoken':$('[name="csrfmiddlewaretoken"]').val()       <form action="/upload_file/" method="post" enctype="multipart/form-data">
  13.     <p><input type="file" name="upload_file_form"></p>
  14.     <input type="submit">
  15. </form>    },
  16.   success: function (data) {
  17.     console.log('成功了')
  18.     console.log(data)           },
  19. })
复制代码
方式2:放在哀求头(个人建议)
  1. $.ajax({
  2.             url: '/csrf_test/',
  3.             method: 'post',
  4.             headers:{'X-CSRFToken':'token值'},  // 注意放到引号里面
  5.             data:{}
  6. }
复制代码
9.6.6 全局使用,局部禁csrf

(1) 在视图函数上加装饰器
  1. from django.views.decorators.csrf import csrf_exempt,csrf_protect
  2. @csrf_exempt
  3. def 函数名(request):  # 加上装饰器后,这个视图函数,就没有csrf校验了
复制代码
(2) 视图类
  1. from django.views.decorators.csrf import csrf_exempt,csrf_protect
  2. from django.utils.decorators import method_decorator
  3. @method_decorator(csrf_exempt,name='dispatch')
  4. class index(View):
  5.     def get(self,request):
  6.         return HttpResponse("GET")
  7.     def post(self,request):
  8.         return HttpResponse("POST")
复制代码
示例(前后端分离 不适用渲染{%csrf_token%}
  1. def login(request):
  2.     if request.method == 'GET':
  3.         print('oooooooopppppppppppp')
  4.         return render(request, 'students/login.html')
  5.     else:
  6.         account = request.POST.get('account')
  7.         pwd = request.POST.get('password')
  8.         user = auth.authenticate(username=account, password=pwd)
  9.         dct = {'state': False, 'msg': '登陆失败'}
  10.         if user:
  11.             auth.login(request, user)
  12.             dct['state'] = True
  13.             dct['msg'] = ''
  14.         return JsonResponse(dct)
  15. def get_tokens(request):
  16.     tok = get_token(request)
  17.     return HttpResponse(tok)
复制代码
  1. [/code]记得要确保get_tokens在白名单中。 当然上面的示例放在头里也可以,当头和体里都找不到的时候才会出题目。
  2. 原理:
  3. 我们可以看到 cookie中的csrf-token和我们的token并不一样
  4. [align=center][img]https://img2024.cnblogs.com/blog/3088220/202407/3088220-20240707164013919-1877346031.png[/img][/align]
  5. 当我们的csrf中间件在头大概数据中找到我们提交的token时,由于每次哀求携带着cookie,它会根据这两组字符串进行比对,解出唯一的secret。(前32位为加密盐,后32位为数据)
  6. 注:不光是我们自己写的网站有这一机制,其它网站也存在,如csdn:
  7. [align=center][img]https://img2024.cnblogs.com/blog/3088220/202407/3088220-20240707164022196-1716788586.png[/img][/align]
  8. [size=3]9.6.7 上传文件[/size]
  9. [b]form表单上传文件[/b]
  10. (记得修改表单提交的编码方式)
  11. [size=4]form表单上传文件[/size]
  12. [code]<form action="/upload_file/" method="post" enctype="multipart/form-data">
  13.     <p><input type="file" name="upload_file_form"></p>
  14.     <input type="submit">
  15. </form>
复制代码
  1. def index(request):
  2.     return render(request,"index.html")
  3. def upload_file(request):
  4.     print("FILES:",request.FILES)# 得到文件对象类字典
  5.     print("POST:",request.POST)# 得到类字典里有csrftoken(如果编码方式没变 还能得到文件名)
  6.     file = request.FILES
  7.     with open(file.name,'wb') as f:
  8.         for i in file:
  9.             f.write(i)
  10.     return HttpResponse("上传成功!")
复制代码
写文件的时候记得用‘wb'  文件名可以直接 request.FILES.get('file').name
9.6.8 Ajax(基于FormData)

FormData是什么呢?
XMLHttpRequest Level 2添加了一个新的接口FormData.利用FormData对象,我们可以通过JavaScript用一些键值对来模仿一系列表单控件,我们还可以使用XMLHttpRequest的send()方法来异步的提交这个"表单".比起普通的ajax,使用FormData的最大优点就是我们可以异步上传一个二进制文件.
所有主流浏览器的较新版本都已经支持这个对象了,比如Chrome 7+、Firefox 4+、IE 10+、Opera 12+、Safari 5+。
  1. <h3>Ajax上传文件</h3>
  2. <p><input type="text" name="username" id="username" placeholder="username"></p>
  3. <p><input type="file" name="upload_file_ajax" id="upload_file_ajax"></p>
  4. <button id="upload_button">提交</button>
  5. {#注意button标签不要用在form表单中使用#}
复制代码
  1. def index(request):
  2.   
  3.     return render(request,"index.html")
  4.   
  5.   
  6. def upload_file(request):
  7.     print("FILES:",request.FILES)
  8.     print("POST:",request.POST)
  9.     return HttpResponse("上传成功!")
复制代码
9.7 ImageField 和 FileField

ImageField 和 FileField 可以分别对图片和文件进行上传到指定的文件夹中。
1.在下面的 models.py 中 :
picture = models.ImageField(upload_to='avatars/', default="avatars/default.png",blank=True, null=True)
注:定义 ImageField 字段时必须订定参数 upload_to这个字段要写相对路径,
这个参数会加在 settings.py 中的 MEDIA_ROOT背面, 形成一个路径, 这个路径就是上 传图片的存放位置,默认在Django项目根路径下,也就是MEDIA_ROOT默认是Django根目录
以是要先设置好 mysite/settings.py中的 settings.py 中的 MEDIA_ROOT
  1. class Userinfo(models.Model):
  2.     name = models.CharField(max_length=32)
  3.     avatar_img = models.FileField("avatars/")
复制代码
  1. username = request.POST.get("username")
  2. #获取文件对象
  3. file = request.FILES.get("file")   
  4. #插入数据,将图片对象直接赋值给字段
  5. user = Userinfo.objects.create(name=username,avatar_img=file)
复制代码
Django会在项目的根目录创建avatars文件夹,将上传文件下载到该文件夹中,avatar字段保存的是文件的相对路径。
2.在 mysite/settings.py中 :
MEDIA_ROOT = os.path.join(BASE_DIR,"media")
MEDIA_URL='/media/'
MEDIA_ROOT:存放 media 的路径, 这个值加上 upload_to的值就是真实存放上传图片文件位置
MEDIA_URL:给这个属性设值之后,静态文件的链接前面会加上这个值,如果设置这个值,则UserInfo.avatar.url主动更换成:/media/avatars/default.png,可以在模板中直接调用:
3.url.py:
  1. from django.views.static import serve
  2. # 添加media 配置
  3. re_path(r'^media/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
复制代码
最后再给大家增补一个用户文件夹路径
  1. def user_directory_path(instance, filename):
  2.     return os.path.join(instance.name,"avatars", filename)
  3. class Userinfo(models.Model):
  4.     name = models.CharField(max_length=32)
  5.     avatar_img = models.FileField(upload_to=user_directory_path)  
复制代码
4.FileField 和 ImageFiled 雷同。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4