那些年,我们在Django web开发中踩过的坑(一)——神奇的‘/’与ajax+iframe上传

一、上传图片并在前端展示

为了避免前端整体刷新,我们采用ajax+iframe(兼容所有浏览器)上传,这样用户上传之后就可以立即看到图片:

上传前:

上传后:

前端部分html:

<form style="display: inline-block" id="upload_img_form" name="form" action="/upload/" method="POST"
          enctype="multipart/form-data">
        {% csrf_token %}
          <a id="fakeFile" class="fake-file">
            <input type="file" name="img" onchange="UploadImage(this);"/>
            <input type="text" name="url" class="hide"/>
        </a>
        <iframe id=‘upload_img_iframe‘ name=‘upload_img_iframe‘ src="" class="hide"></iframe>
    </form>

  别看这么短短的html,坑却不少:

  • 坑one:action="/upload/",当年笔者傻傻的就只写了action="/upload",因为url是这么配的:url(r‘^upload‘, v.upload),结果

    django居然报错了,笔者查了很多资料,最后才知道,原来,虽然url的最后没有加‘/‘,但是django在运行时会自动给url的后面加‘/‘,因此,我们在post提交时,必须在url的最后加‘/‘。

  • 坑two:enctype="multipart/form-data",文件上传相对于其他表单类型出现的概率比较小,而文件上传确是这些表单类型中的异类,它需要在form写上enctype="multipart/form-data"。
  • 坑three:{% csrf_token %},django运行程序时,请求首先会通过中间件,然后才会通过url,django配置文件中有关于跨站请求伪造的中间件:

    MIDDLEWARE_CLASSES = [
        ‘django.middleware.security.SecurityMiddleware‘,
        ‘django.contrib.sessions.middleware.SessionMiddleware‘,
        ‘django.middleware.common.CommonMiddleware‘,
        ‘django.middleware.csrf.CsrfViewMiddleware‘,
        ‘django.contrib.auth.middleware.AuthenticationMiddleware‘,
        ‘django.contrib.auth.middleware.SessionAuthenticationMiddleware‘,
        ‘django.contrib.messages.middleware.MessageMiddleware‘,
        ‘django.middleware.clickjacking.XFrameOptionsMiddleware‘,
    ]
    

      django为用户实现防止跨站请求伪造的功能,通过中间件 django.middleware.csrf.CsrfViewMiddleware 来完成。而对于django中设置防跨站请求伪造功能有分为全局和局部。

    全局:

      中间件 django.middleware.csrf.CsrfViewMiddleware

    局部:

    • @csrf_protect,为当前函数强制设置防跨站请求伪造功能,即便settings中没有设置全局中间件。
    • @csrf_exempt,取消当前函数防跨站请求伪造功能,即便settings中设置了全局中间件。

    中间件‘django.middleware.csrf.CsrfViewMiddleware‘,会查看post请求是否携带token ,如果没有则直接在process_request中return 并报出如下错误:

    此时,我们有几种处理方式:1.直接简单粗暴的注释掉settings中的csrf中间件;2.给接收post请求的views函数加@csrf_exempt(注:from django.views.decorators.csrf import csrf_exempt,csrf_protect),该装饰器的意思是取消当前函数防跨站请求伪造功能;

JavaScript代码: 

function UploadImage(ths) {
        document.getElementById(‘upload_img_iframe‘).onload = UploadImageComplete;  //页面加载完成后执行UploadImageComplete函数
        document.getElementById(‘upload_img_form‘).target = ‘upload_img_iframe‘;  //设置form提交到iframe
        document.getElementById(‘upload_img_form‘).submit(); //#提交到iframe
    }
    function UploadImageComplete() {
        var origin = $("#upload_img_iframe").contents().find("body").text();//#获取图片数据
        var obj = JSON.parse(origin); //#转换成JavaScript对象
            var img = document.createElement(‘img‘); //#创建img标签
            img.src = obj.path; //图片地址
            img.style.width = "200px";
            img.style.height = "180px";
            $("#upload_img_form").append(img);//添加图片
            $(‘#fakeFile‘).addClass(‘hide‘);
            $(‘#reUploadImage‘).removeClass(‘hide‘);
            $(‘#fakeFile‘).find(‘input[type="text"]‘).val(obj.data);//#保存图片地址到隐藏的input标签中
    }

  后台views函数代码:

def upload(request):
    if request.method == ‘POST‘:
        obj = request.FILES.get(‘img‘)#获取图片对象
        pat = os.path.join(‘static‘,‘img‘,‘vote‘,obj.name)#文件打开目录,需要和当前文件路径一致
        f = open(pat,‘wb‘)
        for ch in obj.chunks():
            f.write(ch)
        f.close()
        ret = {‘path‘: ‘/‘+os.path.join(‘static‘,‘img‘,‘vote‘,obj.name)}#django前端文件路径:‘/‘ + 静态文件前缀static + 静态文件下的目录
        import json
        return HttpResponse(json.dumps(ret))#反馈给前端
    return render(request,‘upload.html‘,)

后台和前端js关于文件路径再现一坑:

  注:这里我们将图片保存在静态文件目录static下的img中(静态文件路径的前缀也是static)

  django前端我们希望的引入图片的路径是这样的:/static/img/xx.png,这里需要注意的是static前必须加‘/‘,没错,就是这个神奇的‘/‘,代表当前程序主目录,即配置文件中的BASE_DIR。

  后台我们打开文件写入图片到服务器的路径,这里open路径需要遵循的是python的规则而不是django前端的规则,即python会默认从当前路径开始找,因此直接static/img/xx.png就行,前面不需要加‘/‘。

二、数据库查询结果给前端反馈序列化的问题

  

  json不能序列化django中的datatime、Decimal等数据结构,解决方案:

1.单独转为python数据结构

str( Decimal(‘12.36‘))

  然后通过json进行序列化。

2.直接将数据库查询结果QuerySet对象转化为列表,QuerySet看起来像列表

ret=list(QuerySet对象)result=json.dumps(ret)

  由于json.dumps时无法处理datetime日期,所以可以通过自定义处理器来做扩展,如:

import json
from datetime import date
from datetime import datetime 

class JsonCustomEncoder(json.JSONEncoder): 

    def default(self, field): 

        if isinstance(field, datetime):
            return o.strftime(‘%Y-%m-%d %H:%M:%S‘)
        elif isinstance(field, date):
            return o.strftime(‘%Y-%m-%d‘)
        else:
            return json.JSONEncoder.default(self, field) 

# ds = json.dumps(d, cls=JsonCustomEncoder) 

  这是通过制定json.dumps序列化的类来实现的。

3.使用django的序列化工具serializers

from django.core import serializers

ret = models.BookType.objects.all()

data = serializers.serialize("json", ret)

  

  

  

时间: 2024-10-29 05:09:28

那些年,我们在Django web开发中踩过的坑(一)——神奇的‘/’与ajax+iframe上传的相关文章

Android 开发中踩过的坑之十一: 团队协作的坑

工作中,android的坑很多,一部分是android源码自身的逻辑陷阱, 但跟多的是自己和同事们由于种种原因埋下的坑. 提高面向对象的能力,并恰当的实现在代码中,能够极大的减少坑人和被坑几率. 面向对象的最大特征是复用, 复用的目的是减少工作量,减少错误几率,提高工作效率. 总结几个自己的体会,以自勉: 1 在编码前明确代码的模块框架, 定义最简单的接口. 有人也许说, 书生造反,十年不成, 在工期紧张或者其他类似敏捷编程的大背景下, 先干起来才是硬道理. 事实上, 工期紧张也许正是因为之前的

host不能访问虚拟机内web service中踩到的坑--2017年5月3日

host访问vm中的web service,一直显示不能连接 关闭host和vm中的防火墙,查看host中端口占用情况,都无果 最后发现启动服务的方式错了: python manage.py runserver 0.0.0.0:port 监听所有ip python manage.py runserver 只能在本地访问

Android 开发中踩过的坑之十: 谨慎处理动画的开始和结束

Android提供多种动画机制, 从面相对象的方式到直接实现底层onDraw的方式, 给予了足够的控件实现希望的效果, 无论时使用哪种方式实现动画, 都要谨慎的处理动画的两个状态, 开始和结束 需要关注的问题有: 1 动画开始的时候是否需要重新初始化内存? 对于需要重复展示的动画, 应当避免每次都new新的内存, 否则在动画展示过程, 内存会不断增加, 而gc合适回收, 是不确定的. 也许当gc时, 你已经在OOM的边缘了. 2 动画开始时是否是从某个中间状态开始的? 动画从某个中间状态开始,

Android 开发中踩过的坑之七:尽量避免使用Acitivity当做Context

这坑容易埋, 却不容易发现. 比如启动一个页面, 需要用到一个单例的工具类Utils, 初始化Utils需要一个Context参数, 直接传入Activity.this. 然后这个单例的Utils就会一直持有Activity.this, 导致整个Acitivity不能被GC. 而如果代码中大量的使用Utils, 又不能确认到底谁最先初始化了Utils, 使得内存泄露成了灵异事件难以发现. 所以, 正确的使用方式是: activity.this.getApplicationContext(); g

Android 开发中踩过的坑之六:几个关于View的tips

这几个点, 不算是坑, 但是也确实浪费了我一些时间 1.ScrollView的高设置成"wrap_content"会缩的很小,  ScrollView内只允许嵌套一个View, 并且不要将他的高度设置为"wrap_content", 否则它会缩小到很短的样子. ListView也是一样. 2.ListView中的Item如果有不同的样式,最好使用getItemViewType()来区别 事实上, 在ListView的Item完全可以用一种View布局来控制Visia

Android 开发中踩过的坑之八:多进程问题

这是个需要细心处理的坑. 1 内存: 在manifest中可以对各个组件声明其所在的进程: android:process=":name" 然后对应的Acitivity, Receiver, Service就会运行在相应的进程中. 但是有些类会在所有进程中运行, 比如一些Utils工具类, 比如Application类. 当遇到多个进程并行的时候, 厘清进程所对应的代码, 避免在进程A里跑了进程B的代码. 比如有一个工具类Utils_procA. 只需要在进程A中工作, 那对于进程B来

Android 开发中踩过的坑之二: 16ms的UI线程时间才不会卡顿

AndroidUI卡顿, 是总会遇到的问题, 这个坑经常遇到, 通常在优化时才会重点关注. 通常在Adapter.getView()方法中比较突出. 人眼的原因, 1秒24帧的动画才能感到顺畅. 所以每帧的时间大概有41ms多一点点(1000ms/24). 但是但是, 注意了, 这41ms不是全都留给你java代码, 而是所有java native 屏幕等等的, 最后留给我们用java级别发挥的时间, 只有16~17ms了. 所以,当你优化视觉效果时, 留意UI线程的时间, 超过16ms, 就需

Android 开发中踩过的坑之九: 发布一个aar的注意事项

现在Android支持aar格式发布一个模块, 提供给其他人使用. aar其实是jar和一些资源文件的zip包. 解决了过去jar包不能分享资源的局限. 1 要尽量避免定义内部接口, 这其实是一个编程习惯, 接口interface最好是独立定义, 避免定义在类的内部. 因为当你发布aar时, 内部的接口在混淆后会独立成一个外部的接口Outer$InnerInterface. 然后麻烦来了, 别人在实现这个类的时候必须也写成XXX implement Outer$InnerInterface{}的

Android 开发中踩过的坑之十一: 避免使用Shader做动画

进来优化内存, 发现5.0的系统上, Shader有个不算bug的小问题, Shader在绘制时, 会copy自身, 如果使用shader做动画, 就会不断的copy自身, 不断的增加使用内存. 大概2k/s的速度上涨 sdk放出的源码中有一个@hide的方法copy(), 怀疑就是它导致内存不断增长, 但是查不到调用. 解决方法也简单, gc一下, 或者避免使用shader来做动画.