django文件上传下载
上传
配置settings.py
# 设定文件的访问路径,如:访问http://127.0.0.1:8000/media/就可以获取文件 MEDIA_URL = '/media/' # 设置文件的存储路径,全部存储在media目录下,会和model类中的upload_to属性值拼接 MEDIA_ROOT = os.path.join(BASE_DIR,'media')
models.py
class Img(models.Model): name = models.CharField(max_length=32) # upload_to拼接在MEDIA_ROOT后面,../media/img/article/,内容均存在该目录下 img = models.ImageField(upload_to='img/article/',verbose_name='图片')
upload.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!--enctype属性值修改成"multipart/form-data"--> <form action="" method="post" enctype="multipart/form-data"> {% csrf_token %} <!--type类型要改成file,文件类型--> <input type="text" name="name"> <input type="file" name="img"> <button>上传</button> </form> </body> </html> <!--多文件上传--> <input type="file" name="myfiles" multiple="">
views.py
# 获取上传文件单个插入数据 def upload(request): if request.method == 'POST': # 获取文件名称 img_url = request.FILES.get("img") name = request.POST.get("name") # 存到数据库中,并保存到指定的目录下 img = models.Media(name=name,img=img_url) img.save() return render(request,"youhua.html") # 获取上传文件插入批量数据 def upload(request): if request.method == 'POST': img_list = request.FILES.getlist('img') name = request.POST.get("name") querysetlist = [] for img in img_list: querysetlist.append(models.Media(name=name,img=img)) models.Media.objects.bulk_create(querysetlist) return HttpResponse("上传成功") return render(request, "youhua.html")
ajax上传图片
FormData上传
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script> </head> <body> <input type="file" name="img" id="img"> <input type="button" value="上传" onclick="showImg();"> <script type="text/javascript"> function showImg() { var formdata = new FormData();// 创建一个空的FormData对象,可以使用formdata.append(key,value)来添加数据。 formdata.append('file', document.getElementById('img').files[0]); $.ajax({ url: '/upload/', type: 'post', headers :{ 'x-csrftoken': '{{ csrf_token }}', // 为了通过csrf校验,所以必须带这个过去。 }, data: formdata, // 默认值为true,默认会将发送的数据序列化以适应默认的内容类型。不想转换信息,需要设置为false // 此数据传输的是对象,所以不需要序列化 processData: false, // 不写默认为application/x-www-form-urlencoded只能上传文本,上传文件需要用multipart/form-data类型。 contentType: false, // 不设置内容类型 success: function (data) { alert(data) } }); } </script> </body> </html>
views.py
def upload(request): if request.method == 'POST': img_url = request.FILES.get('file') img = models.Media(name='hello',img=img_url) img.save() ret = '上传成功' return HttpResponse(ret) return render(request,"youhua.html")
页面展示图片
如果涉及到下载/显示资源,就需要添加url
from django.contrib import admin from django.urls import path from imgTest.views import uploadImg, showImg from django.conf.urls.static import static from django.conf import settings # 写法一 urlpatterns = [ path('admin/', admin.site.urls), path('upload/', upload), path('showImg/', showImg) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # 写法二 from django.views.static import serve urlpatterns = [ ... path('showImg/', showImg), url('^media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}) ]
views.py
def showImg(request): obj_list = models.Img.objects.all() print(obj_list) return render(request,'showImg.html',{"obj_list":obj_list})
showImg.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {% for obj in obj_list %} <!--必须要带url,不然路径会错误--> <img src="{{ obj.img.url }}" alt=""> {% endfor %} </body> </html>
文件下载
自定义编写视图下载方法,主要是为了限制用户的下载内容,一定要注意限制用户的下载内容,不然知道路径连代码和数据库都下载了。
urls.py
from app_youhua import views urlpatterns = [ # ...... url('^download/(?P<pk>\d+)/$', views.download, name='download'), ]
html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> {% load static %} </head> <body> {% for obj in obj_list %} <img src="{{ obj.img.url }}" alt=""> <!--自动将数据库中的图片全部显示,可以自定制拼接路径--> <a href="{% url 'download' obj.pk %}">下载</a> <!--url反向解析--> {% endfor %} </body> </html>
使用HttpResponse
from django.http import HttpResponse, Http404 def download(request, pk=None): obj = models.Media.objects.get(pk=pk) filename = str(obj.img) filepath = os.path.join(settings.MEDIA_ROOT, filename) with open(filepath, 'rb') as f: try: response = HttpResponse(f) response['content_type'] = "application/octet-stream" response['Content-Disposition'] = 'attachment; filename=%s' % filename return response except Exception: raise Http404
HttpResponse有个很大的弊端,其工作原理是先读取文件,载入内存,然后再输出。如果下载文件很大,该方法会占用很多内存。对于下载大文件,Django更推荐StreamingHttpResponse和FileResponse方法,这两个方法将下载文件分批(Chunks)写入用户本地磁盘,先不将它们载入服务器内存。
使用StreamingHttpResponse和FileResponse
from django.http import FileResponse, StreamingHttpResponse def download(request, pk=None): obj = models.Media.objects.get(pk=pk) filename = str(obj.img) # filename = request.GET.get('file') # 如果文件名直接通过页面传回 filepath = os.path.join(settings.MEDIA_ROOT, filename) # 文件将会自动关闭,所以不需要使用with语句打开文件 fp = open(filepath, 'rb') response = StreamingHttpResponse(fp) # response = FileResponse(fp) # 默认一次下载4096字节 response['Content-Type'] = 'application/octet-stream' response['Content-Disposition'] = 'attachment;filename="%s"' % filename return response
文件私有化的两种方法
如果你想实现只有登录过的用户才能查看和下载某些文件。
上传文件放在media文件夹,文件名使用很长的随机字符串命名(uuid), 让用户无法根据文件名猜出这是什么文件。视图和模板里验证用户是否已登录,登录或通过权限验证后才显示具体的url。- 简单易实现,安全性不高,但对于一般项目已足够。
- 上传文件放在非media文件夹,用户即使知道了具体文件地址也无法访问,因为Django只会给media文件夹里每个文件创建独立url资源。视图和模板里验证用户是否已登录,登录或通过权限验证后通过自己编写的下载方法下载文件。- 安全性高,但实现相对复杂。
我们定义的下载方法可以下载所有文件,不仅包括.py文件,还包括不在media文件夹里的文件(比如非用户上传的文件)。比如当我们直接访问127.0.0.1:8000/file/download/file_project/settings.py/时,你会发现我们连file_project目录下的settings.py都下载了。
# 简单举例,不让用户下载.py等结尾的文件 from django.http import Http404,FileResponse, StreamingHttpResponse def download(request, pk=None): obj = models.Media.objects.get(pk=pk) filename = str(obj.img) filepath = os.path.join(settings.MEDIA_ROOT, filename) ext = os.path.basename(file_path).split('.')[-1].lower() # cannot be used to download py, db and sqlite3 files. if ext not in ['py', 'db', 'sqlite3']: # 文件将会自动关闭,所以不需要使用with语句打开文件 fp = open(filepath, 'rb') response = StreamingHttpResponse(fp) response['content_type'] = "application/octet-stream" response['Content-Disposition'] = 'attachment;filename="%s"' % filename return response else: raise Http404
django富文本编辑框
下载
pip install django-ckeditor
注册
INSTALLED_APPS = [ ... 'ckeditor', 'ckeditor_uploader', ]
settings中配置
CKEDITOR_UPLOAD_PATH = 'ckeditor/'
配置urls.py
from ckeditor_uploader import views from django.views.static import serve urlpatterns = [ url(r'^media/(?P<path>.*)', serve, {'document_root':settings.MEDIA_ROOT}), # 上传文件 url(r'^ckeditor/upload/', views.upload), url(r'^ckeditor/', include('ckeditor_uploader.urls')), ]
models.py使用富文本编辑框字段
from ckeditor_uploader.fields import RichTextUploadingField class Article(models.Model): title = models.CharField(max_length=32) detail = models.OneToOneField('ArticleDetail',on_delete=models.CASCADE) class ArticleDetail(models.Model): content = RichTextUploadingField(verbose_name='文章详情')
模板中使用
{{ field }} 使用ModelForm富文本编辑框的字段, # 导入js样式,只要需要显示富文本编辑框就要导入 <script src="{% static 'ckeditor/ckeditor/ckeditor.js' %}"></script> <script src="{% static 'ckeditor/ckeditor-init.js' %}"></script>
关联表同时显示富文本编辑框
比如:编辑内容时,肯定是连同关联的详情内容一同填写。
# 视图函数 def article_add(request): # 对两个form表单进行实例化 form_obj = ArticleForm() detail_form = ArticleDetailForm() if request.method == 'POST': # 先校验并保存被关联方 detail_form = ArticleDetailForm(request.POST) if detail_form.is_valid(): detail_form.save() qd = request.POST.copy() # request.POST是有序字典,默认是不可编辑,所以进行深拷贝后编辑 # 找到被关联方提交此条数据的pk qd['detail'] = detail_form.instance.pk # 校验并保存被关联方 form_obj = ArticleForm(data=qd, files=request.FILES) # 如存在文件类,需单独传参 if form_obj.is_valid(): form_obj.save() return redirect(reverse('backend:article_list')) # 如果被关联方未通过校验,且关联方通过校验并保存,则删除关联方保存的数据 if detail_form.is_valid() and detail_form.instance: detail_form.instance.delete() title = '新增文章' return render(request,'backend/article_form.html'{'form_obj':form_obj,'title':title,'detail_form':detail_form})
from django import forms from repository import models class ArticleForm(forms.ModelForm): class Meta: model = models.Article fields = '__all__' def __init__(self,*args,**kwargs): super(ArticleForm, self).__init__(*args,**kwargs) for field in self.fields.values(): # 用来控制不使用'form-control'样式的 if isinstance(field.widget,forms.ClearableFileInput): continue field.widget.attrs['class'] = 'form-control' class ArticleDetailForm(forms.ModelForm): class Meta: model = models.ArticleDetail fields = '__all__'