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__'
posted @ 2019-11-07 10:13  Lowell  阅读(495)  评论(0编辑  收藏  举报