Django文件上传下载与富文本编辑框
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
pip install pillow # 图形化展示用,非必须
注册
INSTALLED_APPS = [
...
'ckeditor',
'ckeditor_uploader',
]
settings中配置
CKEDITOR_UPLOAD_PATH = 'ckeditor/'
CKEDITOR_CONFIGS = {
'default': {
'toolbar': (
['div', 'Source', '-', 'Save', 'NewPage', 'Preview', '-', 'Templates'],
['Cut', 'Copy', 'Paste', 'PasteText', 'PasteFromWord', '-', 'Print', 'SpellChecker', 'Scayt'],
['Undo', 'Redo', '-', 'Find', 'Replace', '-', 'SelectAll', 'RemoveFormat'],
['Form', 'Checkbox', 'Radio', 'TextField', 'Textarea', 'Select', 'Button', 'ImageButton', 'HiddenField'],
['Bold', 'Italic', 'Underline', 'Strike', '-', 'Subscript', 'Superscript'],
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', 'Blockquote'],
['JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock'],
['Link', 'Unlink', 'Anchor'],
['Image', 'Flash', 'Table', 'HorizontalRule', 'Smiley', 'SpecialChar', 'PageBreak'],
['Styles', 'Format', 'Font', 'FontSize'],
['TextColor', 'BGColor'],
['Maximize', 'ShowBlocks', '-', 'About', 'pbckcode'],
),
}
}
配置urls.py
from ckeditor_uploader import views
from django.views.static import serve
from django.contrib.auth.decorators import login_required
from django.views.decorators.cache import never_cache
from ckeditor_uploader import views as ckeditor_views
urlpatterns = [
url(r'^media/(?P<path>.*)', serve, {'document_root':settings.MEDIA_ROOT}), # 图片可下载
# 富文本编辑,下面的url只能admin用户上传图片
# path('ckeditor/', include('ckeditor_uploader.urls')),
# 解决非admin权限用户无法上传图片问题
path('ckeditor/upload/', login_required(ckeditor_views.upload), name='ckeditor_upload'),
path('ckeditor/browse/', never_cache(login_required(ckeditor_views.browse)), name='ckeditor_browse'),
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
# 在开发过程中提供静态文件static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
# 在开发过程中提供用户上传的文件static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
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__'