目录
一、Django简介:
1、Web框架本质
我们可以这样理解:所有的Web应用本质上就是一个socket服务端,而用户的浏览器就是一个socket客户端。 这样我们就可以自己实现Web框架了。
2、Django版本
这里我学习的是Django的版本是1.11.11版本,下面为大家简单介绍1.x 和2.x、 3.x直接的区别
1、django 1.x:路由层使用的是url方法 而在django 2.x和3.x版本中路由层使用的是path方法
url()第一个参数支持正则
url(r'^index/',index)
path()第一个参数是不支持正则的,写什么就匹配什么(精准匹配)
path('index/',index)
如果你在2.x版本中不习惯使用path,那么也提供了另外一个方法
from django.urls import path,re_path
re_path(r'^index/',index)
2.x和3.x里面的re_path就等价于1.x里面的url 2、虽然path不支持正则 但是它内部支持五种转换器
str,匹配除了路径分隔符(/)之外的非空字符串,这是默认的形式
int,匹配正整数,包含0。
slug,匹配字母、数字以及横杠、下划线组成的字符串。
uuid,匹配格式化的uuid,如 075194d3-6885-417e-a8a8-6c931e272f00。
path,匹配任何非空字符串,包含了路径分隔符(/)(不能用?)
path('index/<int:id>/',index)
def index(request,id):
print(id,type(id)) # 11 <class 'int'>
将第二个路由里面的内容先转成整形然后以关键字的形式传递给后面的视图函数
url(r'^index/(/d+/)',index)
def index(request,xx):
print(xx,type(xx)) # 11 <class 'str'>
3、除了有默认的五个转换器之外 还支持自定义转换器(了解)
class MonthConverter:
regex='\d{2}' # 属性名必须为regex
def to_python(self, value):
return int(value)
def to_url(self, value):
return value # 匹配的regex是两个数字,返回的结果也必须是两个数字
from django.urls import path,register_converter
from app01.path_converts import MonthConverter
# 先注册转换器
register_converter(MonthConverter,'mon')
from app01 import views
urlpatterns = [
path('articles/<int:year>/<mon:month>/<slug:other>/', views.article_detail, name='aaa'),
]
4、模型层里面1.x外键默认都是级联更新删除的 但是到了2.x和3.x中需要你自己手动配置参数
# 1.x版本
models.ForeignKey(to='Publish')
# 2.x版本
models.ForeignKey(to='Publish',ON_delete=models.CASCADE)
3、 注意事项
如何让你的计算机能够正常的启动django项目 1、计算机的名称不能有中文 2、一个pycharm窗口只能开一个项目 3、项目里面所有的文件尽量不要出现中文 4、python解释器尽量使用3.4-3.6之间的版本 如果你的项目报错 点击最后一个报错信息 去源码中把逗号去掉 djaongo安装: pip install django==1.11.11 验证是否安装成功: 终端输入django-admin看看是否有反应 1、命令行操作: 1、创建django项目: django-admin startproject 项目名 2、启动django项目 一定要先切换到项目目录下 python manage.py runserver django是一款专门用来开发app的web框架 django类似于一个大学(空壳子) app就类似于大学里面的学院(具体功能) 在前期我们要使用django提交post请求的时候 需要去配置文件中注释掉一行代码
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
pycharm中创建应用:
python manage.py startapp appname
或者点tool下的 Run manage.py Task....在此窗口中输入命令有自带提示
4、python 三大主流web框架简单介绍及区别
django: 特点:大而全 自带的功能特别特别多 类似于航空母舰 不足之处:有时候过于笨重 flask: 特点:小而精 自带的功能特别少 类似于游骑兵 第三方的模块特别特别多 不足之处:比较依赖于第三方库 tornado 特点:异步非阻塞 支持高并发 甚至可以开发游戏服务器 一个框架可以拆分成三个部分: A:socket部分 B:路由与视图函数对应关系(路由匹配) C:模板语法 django: A:用的是别人的(wsgire模块) B:用的是自己的 C:用的是自己的(没有jinja2好用,但是也很方便) flask: A:用的是别人的(werkzeug模块 内部其实还是wsgire模块) B:自己写的 C:用的别人的(jinja2模块) tornado: A、B、C都是自己写的
5、静态文件配置
我们将Html文件默认都放在templates文件下 我们将网站所用的静态文件默认都放在static文件夹下 静态文件 前端已经写好了的 能够直接调用使用的文件 网站写好的js文件 网站写好的css文件 网站用到的图片文件 第三方前端框架 django默认不会自动帮你创建static文件夹 需要你自己手动创建 一般情况下我们在static文件夹内还会进一步的划分处理 -static --js --css --img --other(其它第三方文件) 在浏览器中输入url能够看到对应的资源的原因: 是因为后端提前开设了该资源的接口 如果访问不到资源 说明后端没有开设该资源的接口 当你在写django项目的时候 可能会出现后端代码修改了但是前端页面没有变化的情况 1、你在同一个端口开了好几个django项目 一直在跑的其实是第一个django项目 2、浏览器缓存问题 settings network disable cache 勾选上
STATIC_URL = '/static/' #类似于访问静态文件的令牌
'''如果你想要访问静态文件 你就必须以static开头
/static/bootstrap-3.4.1-dist/css/bootstrap.min.css
/static/令牌
去列表里面从上往下依次查找,查找到一个就不会继续往下找了,所以可能会出现替换的情况
bootstrap-3.4.1-dist/css/bootstrap.min.css
都没有才会报错
'''
# 静态文件配置
STATICFILES_DIRS=[
os.path.join(BASE_DIR,'static1'),
os.path.join(BASE_DIR,'static2'),
os.path.join(BASE_DIR,'static4'),
]
{#静态文件动态解析#}
{% load static %}
<link rel="stylesheet" href={% static "bootstrap-3.4.1-dist/css/bootstrap.min.css" %}>#}
<script src={% static "bootstrap-3.4.1-dist/js/bootstrap.js" %}></script>
6、小白必须三板斧
HttpResponse: 返回字符串类型的数据 render: 返回html文件 并且在返回浏览器之前还可以给html文件传值 redirect: 重定向 return redirect('http:www.baidu.com') return redirect('/home/') 视图函数必须返回一个HttpResponse对象 这句话是对的,研究三者的源码即可得出结论 render简单内部原理:
from django.templates import Templates,Context
res=Templates('<h1>{{user}}</h1>')
con=Context({'user':{'username':'jason','password':'1234'}})
ret=res.render(con)
print(ret)
return HttpResponse(ret)
简单理解重定向:
7、虚拟环境
在开发环境中 我们会给每一个项目配备一个该项目独有的解释器环境
该项目内只有该项目用到的模块 用不到一概不装
虚拟环境:
你每创建一个虚拟环境就类似于重新下载了一个纯净的python解释器
但是虚拟环境不要创建太多 是需要消耗硬盘空间的
扩展:
每一个项目都需要用到很多模块 并且每个模块版本可能还不一样
那我们该如何安装呢?
开发当中我们会给每一个项目配备一个requirements.txt文件:
里面书写了该项目所有的模块即版本
你只需要直接输入一条命令即可一键安装所有模块即版本
二、路由层
1、路由匹配
url(r'text',views.text),
url(r'textadd',views.textadd)
'''
url方法第一个参数是正则表达式
只要第一个参数正则表达式能够匹配到内容 那么就会立刻停止往下匹配
直接执行对应的视图函数
'''
内容为textadd时 第一个进行正则表达式能够匹配,则执行text对应的视图函数而不会往下继续匹配 url(r'text/',views.text), url(r'textadd/',views.textadd) 你在输入url的时候会默认加/的原因: django内部帮你做重定向,第一次匹配不行,url加/再匹配一次 若想取消django自动加/: 在settings文件中写上一行代码: APPEND_SLASH=False 最严谨的书写格式: url(r'^text/$',views.text), url(r'^textadd/$',views.textadd) ps:^表示以什么开头 $表示以什么结尾 若想启动django项目后点击http://127.0.0.1:8000/首页不报错进入一个页面可以: url(r'^$',views.home) 然后在视图层定义home函数 路由分发 django的每一个应该都可以有自己的templates文件夹 urls.py static文件夹 正是基于上述特定 django能够非常好的做到分组开发(每个人只写自己的app) 当一个django项目中的url特别多的时候 总路由urls.py代码会非常冗余不好维护 这个时候 也可以利用路由分发来减轻总路由的压力 利用路由分发之后 总路由不再做路由与视图函数的直接对应关系 而是做一个分发处理: 识别当前url是属于哪个应用的 直接分发给对应的应用去处理 总路由
from django.conf.urls import url,include
from django.contrib import admin
# from app01 import urls as app01_urls
# from app02 import urls as app02_urls
urlpatterns = [
url(r'^admin/', admin.site.urls),
# 路由分发
# url(r'^app01/',include(app01_urls)),# 只要url前缀是app01开头 全部交给app01处理
# url(r'^app02/',include(app02_urls)),# 只要url前缀是app02开头 全部交给app02处理
# 终极写法 推介使用(注意事项:总路由里面的url千万不能加$结尾)
url(r'^app01/',include('app01.urls')),
url(r'^app02/',include('app02.urls')),
]
子路由
# app01 urls.py
from django.conf.urls import url
from app01 import views
urlpatterns = [
url(r'^reg/', views.reg)
]
# app02 urls.py
from django.conf.urls import url
from app02 import views
urlpatterns =[
url(r'^reg/', views.reg)
]
2、名称空间
当多个应用出现了相同的别名 我们研究反向解析会不会自动识别应用前缀:
正常情况下的反向解析是没有办法自动识别前缀的
名称空间:
总路由:
url(r'^app01/',include('app01.urls',namespace='app01')),
url(r'^app02/',include('app02.urls',namespace='app02'))
解析的时候:
# app01_urls
urlpatterns = [
url(r'^reg/', views.reg,name='reg')
]
# app02_urls
urlpatterns = [
url(r'^reg/', views.reg,name='reg')
]
reverse('app01:reg')
reverse('app02:reg')
{% url 'app01:reg' %}
{% url 'app02:reg' %}
其实只要保证名字不冲突 就没有必要使用名字空间 一般情况下 有多个app的时候我们在起别名的时候会加上app前缀 这样的话就能够确保多个app之间名字不冲突的问题
3、无名分组与有名分组
分组:就是给某一段正则表达式用小括号括起来
无名分组:
url(r'^text/(\d+)/',views.text) #(\d+)将\d+匹配到的内容作为位置参数 \d为数字 +表示任意多个
def text(request,xx):
print(xx)
return HttpResponse('text')
无名分组就是将括号内正则表达式匹配到的内容当作位置参数传递给后面的视图函数
有名分组:
可以给正则表达式起一个别名
url(r'^textadd/(?P<year>\d+)',views.textadd),# 有名分组
def textadd(request,year):
print(year)
return HttpResponse('textadd')
有名分组就是将括号内正则表达式匹配到的内容当作关键字参数传递给后面的视图函数
无名与有名不能混用,但是同一个分组可以使用多次
url(r'^index/(\d+)/(\d+)',views.index),
url(r'^index2/(?P<year>\d+)/(?P<age>\d+)',views.index2)
def index(request,*args):
print(args) #('11', '22')
return HttpResponse('index')
def index2(request,**kwargs):
print(kwargs) #{'year': '11', 'age': '22'}
return HttpResponse('index2')
4、反向解析
通过一些方法得到一个结果 该结果可以直接访问对应的url触发视图函数 先给路由与视图函数起一个别名 url(r'^func/',views.func,name='xx') 反向解析: 后端反向解析
from django.shortcuts import reverse
reverse('xx')
前端反向解析
<a href={{% url 'xx' %}}>11</a>
无名分组反向解析: url(r'^index/(\d+)/',views.index,name='xx') 前端:
{% url 'xx' 12 %}"
后端:
reverse('xx',args=(1,))
这个数字写代码的时候应该放什么?
数字一般情况下放的是主键值 数据的编辑和删除
如:
url(r'^edit/(\d+)/',views.edit,name='xx')
def edit(request,edit_id):
reverse('xx',args=(edit_id,))
{%for user_obj in user_querset %}
<a href="{% url 'xx' user_obj.id'%}">编辑</a>
{%endfor%}
有名分组反向解析
url(r'^func/(?P<year>\d+)/', views.func, name='oo')
前端:
{#有名分组反向解析 方法一#}
<a href="{% url 'oo' year=11 %}">11</a>
{#有名分组反向解析 方法二#}
<a href="{% url 'oo' 11 %}">22</a>
后端:
# 有名分组反向解析 写法一(正规)
# print(reverse('oo', kwargs={'year': 11}))
# 简便写法
print(reverse('oo',args=(11,)))
三、视图层
1、CBV和FBV
我们之前写过的都是基于函数的view,就叫FBV。还可以把view写成基于类的。
视图函数既可以是函数(FBV)也可以是类(CBV)
CBV:
#CBV路由
url(r'^login/',views.MyLogin.as_view())
class MyLogin(View):
def get(self,request):
return render(request,'form.html')
def post(self,request):
return HttpResponse('post方法')
FBV和CBV各有千秋 CBV特点: 能够直接根据请求方式的不同直接匹配到对应的方法执行
2、requesr对象方法
request.method:获取请求方式
request.POST:用来获取post请求提交的普通键值对数据
request.GET:包含所有HTTP GET参数的类字典对象
request.FILES:获取文件
request.body:原生的浏览器发过来的二进制数据
request.path:能获取url但不能获取问号后面的参数
request.path_info:能获取url但不能获取问号后面的参数
request.get_full_path:能够获取完整的url及问号后面的参数
print(request.path) #/app01/ab_file
print(request.path_info)#/app01/ab_file
print(request.get_full_path())#/app01/ab_file/?username=jason
3、JsonResponse对象
json格式的数据有什么用?
前后端数据交互需要使用到json作为过度 实现跨语言传输数据
def ab_json(request):
import json
from django.http import JsonResponse
l=[11,22,33,44]
user_dic={'username':'jason老师','password':'1234'}
# 先转成json格式字符串
# 设置ensure_ascii=False,就不会自动转为ascii码
# json_str=json.dumps(user_dic,ensure_ascii=False)
# 将字符串返回
# return HttpResponse(json_str)
# 读源码掌握用法
# return JsonResponse(user_dic,json_dumps_params={'ensure_ascii':False})
return JsonResponse(l,safe=False) # 默认只能序列化字典,序列化其它需要加safe参数
四、模板层
1、模板语法传值:
{{}}:变量相关 {% %}:逻辑相关 后端:
def index(request):
# 模板语法可以传递的后端python数据类型
n=11
f=11.11
s='急啊急啊'
b=True
l=['aa','bb','cc','dd']
t=(11,22,33,44)
d={'username':'jason','age':[18,20,22,25,{'info':'nb'}]}
se={'jaja','haha','meme','afaf'}
def func():
print('我被执行了')
return 'hahahhaah'
class Myclass(object):
def get_self(self):
return 'self'
@staticmethod
def get_func():
return 'func'
@classmethod
def get_class(cls):
return 'class'
# 对象被展示到html页面上 就类似于执行了打印操作也会触发__str__方法
def __str__(self):
return '触发了'
obj=Myclass()
return render(request,'index.html',locals())
前端:
<p>{{ n }}</p>
<p>{{ f }}</p>
<p>{{ s }}</p>
<p>{{ b }}</p>
<p>{{ l }}</p>
<p>{{ d }}</p>
<p>{{ t }}</p>
<p>{{ se }}</p>
{#传递函数名会自动加括号调用 但是模板语法不支持给函数传额外的参数 #}
<p>{{ func }}</p>
{#传类名的时候也会自动加括号调用(实例化) #}
{#内部能够自动判断出当前变量名是否可以加括号调用 如果可以就会自动执行 针对的是函数名和类名#}
{{ Myclass }}
{{ obj }}
{{ obj.get_class }}
{{ obj.get_func }}
{{ obj.get_self }}
django模板语法的取值 是固定的格式 只能采用"句点符". {{ d.username }} ->jason {{ d.hobby.4.info }} ->nb {{ l.0 }} ->aa 即可以.键也可以.索引 还可以两者混用
2、过滤器:
(过滤器最多只能有两个参数) 类似于是模板语法内置的 内置方法 django内置有60+过滤器 基本语法: {{数据|过滤器:参数}}
<h1>过滤器</h1>
{#统计长度#}
<p>{{ s|length }}</p>
{#默认值(第一个参数布尔值是True就展示第一个参数的值否则展示冒号后门的值)#}
<p>{{ b|default:'哈哈哈哈' }}</p>
{#文件大小(自动转换成KB MB GB的格式)#}
<p>{{ file_size|filesizeformat }}</p>
{#日期格式化#}
<p>{{ current_time|date:'Y-m-d H:i:s' }}</p>
{#切片操作(支持步长)#}
<p>{{ l|slice:'0:4:2' }}</p>
{#切取字符(包含三个点)#}
<p>{{ info|truncatechars:9 }}</p>{#受 Rdio...#}
{#切取单词(不包含三个点 按照空格切)#}
<p>{{ egl|truncatewords:6 }}</p>{#my name is dad and I...#}
{#移除特定的字符#}
<p>{{ msg|cut:' ' }}</p>{#ILoveYouAndYou?#}
{#拼接操作#}
<p>{{ l|join:'$' }}</p>{#aa$bb$cc$dd#}
{#拼接操作(加法)#}
<p>{{ n|add:10 }}</p>{#21#}
<p>{{ s|add:msg }}{#急啊急啊I Love You And You?#}</p>
{#取消转义#}
<p>{{ hh|safe }}</p>
{#后端转义#}
<p>{{ hh }}</p>
转义: 前端:
<p>{{ hh|safe }}</p>
后端:
hh='<h1>敏敏</h1>'
# 后端转义:
from django.utils.safestring import mark_safe
res=mark_safe(hh)
以后你在写全栈项目的时候 前端代码不一定非要在前端页面书写 也可以先在后端写好 然后传递给前端页面
3、标签:
for循环
{% for foo in l %}
<p>{{ forloop }}</p>
{'parentloop': {}, 'counter0': 0, 'counter': 1, 'revcounter': 4, 'revcounter0': 3, 'first': True, 'last': False}
<p>{{ foo }}</p> 一个个元素
{% endfor %}
if判断
{% if b %}
<p>结果为true</p>
{% elif s %}
<p>结果也为true</p>
{% else %}
<p>结果为false</p>
{% endif %}
for与if混用:
{% for foo in l %}
# l=['aa','bb','cc','dd']
{% if forloop.first %}
<p>这是第一个for循环</p>
{% elif forloop.last %}
<p>这是最后一次</p>
{% else %}
<p>{{ foo }}</p>
{% endif %}
{% empty %}
<p>for循环的可迭代对象内部没有元素 根本没法循环</p>
{% endfor %}
结果:
'''
这是第一个for循环
bb
cc
这是最后一次
'''
补充:对字典的循环:
{% for key in d.keys %}
<p>{{ key }}</p>
{% endfor %}
{% for value in d.values %}
<p>{{ value }}</p>
{% endfor %}
{% for item in d.items %}
<p>{{ item }}</p>
{% endfor %}
4、with起别名:
{% with d.age.4.info as nb %}
<p>{{ nb }}</p>
在with语法内就可以通过as后面的别名快速的使用到前面非常复杂获取数据的句式
{% endwith %}
5、自定义过滤器、标签、inclusion_tag:
先三步走: 1、在应用下创建一个名字必须叫templatetags文件夹 2、在该文件夹内创建一个任意名称的py文件 3、在该py文件内必须先书写下面两句话 from django import template register=template.Library() 自定义过滤器:
# 自定义过滤器
@register.filter(name='myfilter')
def my_sum(v1,v2):
return v1+v2
# 页面使用:
{% load mytag %}
<p>{{ n|myfilter:66 }}</p>
{# 11+66=77#}
自定义标签: 类似于自定义函数
# 自定义标签(参数可以有多个)
@register.simple_tag(name='mysimpletag')
def index(a,b,c,d):
return '%s-%s-%s-%s'%(a,b,c,d)
# 页面使用 标签多个参数彼此之间空格隔开
<p>{% mysimpletag 'jason' 12 12 12 %}</p>
自定义inclusion_tag
内部原理:
先定义一个方法
在页面上调用该方法 并且可以传值
给方法会生成一些数据然后传递给一个html页面
之后将渲染好的结果放到调用的位置
# 自定义inclusion_tag
@register.inclusion_tag('left_menu.html')
def left(n):
data=['第{}项'.format(i) for i in range(n)]
# 第一种传值方式
# return {'data':data}
# 第二种传值方式
return locals() #将data传递给left_menu.html
index.html页面:
{% left 10 %}
left_menu.html页面:
<ul>
{% for datum in data %}
<li>{{ foo }}</li>
{% endfor %}
</ul>
总结:当html页面某一个地方的页面需要传参数才能够动态的渲染出来,并且在多个页面上都需要使用到该局部 那么就考虑将该局部页面做成inclusion_tag形式
6、模板的继承:
自己先选好想要继承的模板页面 {% extends 'home.html' %} 继承了之后子页面跟模板页面长的是一模一样的 你需要在模板页面上划定可以被修改的区域
{% block content %}
模板内容
{% endblock %}
子页面就可以申明想要修改哪块划定了的区域
{% block content %}
子页面
子版页面除了可以自己写自己之外 还可以继续使用模板的内容:
{{ block.supper }}
{% endblock %}
一般情况下模板页面上应该至少有三块可以被修改的区域 1、css区域
{% block css %}
<style>
h1 {
color: plum;
}
</style>
{% endblock %}
2、html区域
{% block content %}
<h1 class="text-center">注册页面</h1>
<form action="">
<p>username:<input type="text" class="form-control"></p>
<p>password<input type="text" class="form-control"></p>
<input type="submit" class="btn btn-danger">
</form>
{% endblock %}
3、js区域
{% block js %}
<script>
alert('注册')
</script>
{% endblock %}
每个子页面都可以有自己独有的css代码、html代码、js代码 一般情况下 模板的页面上划定的区域越多 那么改模板的扩展性越高 但是如果太多的话还不如自己写 模板的导入: 将页面的某一个局部当成模块的形式 哪个地方需要就可以直接导入使用即可
<p>模板的导入</p>
{% include 'washai.html' %}
五、补充
1、Django连接数据库
默认用的是sqkite3
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
django连接MYSQL: 1、第一步配置文件中配置
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME':'tmp',
'USER':'root',
'PASSWORD':'200145ln',
'HOST':'127.0.0.1',
'PORT':3306,
'CHARSET':'utf8'
}
}
2、代码声明 django默认用的是mysqldb模块连接MYSQL 但是该模块的兼容性不好,需要手动改为用pymysql连接 因此需要告诉django不要用默认的mysqldb还是用pymysql 在项目名下的init.py或者任意的应用名下的init.py文件中书写以下代码都可以
import pymysql
pymysql.install_as_MySQLdb()
2、伪静态
静态网页: 数据是写死的 万年不变 伪静态: 将一个动态网页伪装成静态网页 伪装的目的: 增大本网站的seo查询力度 并且增加搜索引擎收藏本网站的概率 搜索引擎本质就是一个巨大的爬虫程序
3、WSGI与wsgiref、uwsgi的关系
django自带的wsgiref模块 本身能够支持的并发量很小 最多1000左右 上线之后会换成uwsgi WSGI是协议 wsgire和uwsgi是实现该协议的功能模块
4、MTV与MVC模型
MTV:Django号称是MTV模型 M:models T:templates V:views MVC:其实Django本质也是MVC M:models V:views C:controller
5、form表单上传文件及后端获取
form 表单上传文件类型数据 1、method必须指定post 2、enctype必须换成formdata 前端:
<form action="" method="post" enctype="multipart/form-data">
<p>username<input type="text" name="username"></p>
<p>file<input type="file" name="file"></p>
<input type="submit">
后端获取数据:
def ab_file(request):
if request.method=='POST':
# print(request.POST)# 只能获取普通的键值对数据,不能获取文件
print(request.FILES) # 获取文件数据
# < MultiValueDict: {'file': [ < InMemoryUploadedFile: 宝塔.docx(application / octet - stream) >]} >
file_obj=request.FILES.get('file')#文件对象
# 获得文件名
print(file_obj.name)# 宝塔.docx
with open(file_obj.name,'wb')as f:
for line in file_obj.chunks():# 推荐加上chunks方法 其实跟不加是一样的 都是一行行的读取
f.write(line)
return render(request,'form.html')