Django个人工作总结
API:https://docs.djangoproject.com/zh-hans/2.1/
DCIM:https://www.dicomstandard.org/current/
ADMIN:https://www.cnblogs.com/caseast/p/5909987.html
最重要的:大脑不清晰,想用ajax请求将数据传递后台,但是还想跳转页面,这目前以我的技术还达不到。后台是没办法改变浏览器地址的,后台只能将数据渲染到页面上,而只有你浏览器访问页面了才看得到,不访问,后台怎么渲染你还是看不到,这就是后台不能帮你在浏览器跳转所导致的问题。所以只有修改浏览器地址了。
毛毛给的建议是使用window.location.href修改浏览器地址,细细想,如果我用ajax请求后台,将数据提交到后台后,然后将后台处理的数据渲染到另一个页面上,然后在ajax的success里使用window.location.href去修改浏览器地址,按理说应该也是行得通的,没试过。
from django.views.decorators.csrf import csrf_exempt @csrf_exempt
<!--病例表单--> <form id="case_form"> {% csrf_token %} </form>
request.POST
由于前台到后台django是使用QueryDict封装的,request.POST的结果是一个QueryDict,这是一个特殊的字典,有关该字典,可以看下面的链接了解。
https://www.cnblogs.com/scolia/p/5634591.html
还有就是后台提交的数据在前台变成乱码的问题,只需要在重定向后添加:
return render(request, 'survey/hys_result.html', context,content_type="application/json,charset=utf-8")
content_type="application/json,charset=utf-8" 即可,这是用于返回json格式的,如果不是用于ajax规定的json把application/json去掉即可。
from django.shortcuts import render from django.http import HttpResponse from django.views.decorators.csrf import csrf_exempt import json def index(request): return render(request, 'index.html') @csrf_exempt def from_case(request): if request.method == 'POST': sex = request.POST['sex'] print(sex) return HttpResponse(json.dumps({'sex': 123}), content_type='application/json')
/*表单提交事件*/ $('#send').click(function () { var data = $('#from_case').serialize(); $.ajax({ type: 'POST', url: '/sdd/from_case/', // 链接地址以/开头以/结尾 async:false, data: data, dataType: 'json', success: function (result) { console.log(result); }, error:function (error) { console.log(error) } }) return false; // 阻止button按钮的默认提交事件 })
from django.urls import path from . import views urlpatterns = [ path(r'', views.index, name='index'), path(r'sdd/from_case/', views.from_case, name='from_case'), ] app_name = 'sdd'
每次点击当前li,页面会变长,但是滚动条不会自动向下滚动,所以每次都要手动向下拉,很烦人。
$(window).scroll(function(){ //console.log(window.pageYOffset); pos = window.pageYOffset; })
知道了每次滚动的距离是多少就好办了,只要我每次点击当前li,只要让滚动条自动向下前进一定的距离就可以实现想要的效果了,那怎么设置滚动条的距离?
var autoGo = function (pos) { $("html,body").animate({ scrollTop: pos },speed); };
页面每次滚东pos距离,pos的单位是像素【字符串】,speed代表滚动的速度以毫秒级为单位的【是个数值】
var pos = 0; $(window).scroll(function(){ //console.log(window.pageYOffset); pos = window.pageYOffset; }) var autoGo = function (pos) { $("html,body").animate({ scrollTop: pos },0); }; //first代表第一次点击,第一次点击前进的距离多一些 if(first == 0){ first++; pos = pos + 356 + 'px'; }else{ pos = pos + 127 + 'px'; } //console.log(pos); autoGo(pos);
做表单提交时遇到的问题:在form上加onsubmit = "return check(this)",不进入check方法,check方法返回ture则提交表单,否则不提交。换了一种方式,使用submit按钮的onclick方法,但是有个要求就是submit按钮的id不能为"submit",否则报form.submit();is not a function
{% if item.importent == True %} {% if forloop.counter0|divisibleby:2 %} <li class="title">{{tent}}</li> {% else %} <li>{{tent}}</li> {% endif %} {% else %} <li>{{tent}}</li> {% endif %}
var form = document.getElementById("myfrom"); if(form.sex.value == ''){ alert("请选择性别!"); return false; }
<form action="/survey/survey_hys_result" method="post" id="myfrom"> ****** <input type="submit" id="sub" value="测试中..."> </form> sub.onclick = function() { if(isLast < 59) { alert("请先完成测试"); if(isLast == 0){ pos = 0 + 'px'; autoGo(pos); }else{ pos = 356 + 127*isLast + 'px'; autoGo(pos); } return false; } var form = document.getElementById("myfrom"); if(form.sex.value == ''){ alert("请选择性别!"); return false; } if(form.age.value == ''){ alert("请选择年龄!"); return false; } form.submit(); return ture; }
我在使用post传递dicm影像数据时遇到的问题:Request body exceeded settings.DATA_UPLOAD_MAX_MEMORY_SIZE,由于默认django允许post传递的数据量为2.5MB
DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880
要在settings里配置media上传文件路径,当上传文件时它会自动在项目中创建/static_in_env/upload路径,上传的文件将放在upload/dcm下
在这里说一下我的上传路径也必须要前端能访问,不然前端不能通过url拿到dcm文件,也百度了,也看小叶之前代码的配置了,才知到其实不用那么复杂。首先:由于我的上传路径是和实体类绑定的,所以我必须要使用MEDIA_ROOT
上传service代码:https://blog.csdn.net/wanpengsen4890/article/details/81914135
有关django表单对象:https://www.cnblogs.com/zongfa/p/7709639.html
media上传路径的配置https://www.cnblogs.com/yinxin/p/9076894.html
MEDIA_ROOT, MEDIA_URL, STATIC_ROOT, STATIC_URL的介绍:
https://blog.csdn.net/geerniya/article/details/78958243
{{ if info.就诊.重点检查项目 }} <div class="describe-project"> <p class="item-name">重点检查项目</p> <p class="item-content padding-l-20"> {{ each info.就诊.重点检查项目 val key }} {{@ val }} {{ /each }} </p> </div> {{ /if }} // 也可以直接在script中使用 <script> console.log({{info}}) </script>
但是我拿到详情页后又要去处理这个dcm文件(split操作),所以我又要将这个dcm的id在传给split,我尝试了几种写法,如:
结果都是404,我感觉可能有问题的就是这个<int:id>了,而且我这样写浏览器端的路径是相对路径:
http://localhost:8000/12/detail/split
http://localhost:8000/12/detail/12/split
根本和我的url匹配不上,最后我先从浏览器端开始,使用绝对路径,所以我就用到了request对象,其document地址为:https://docs.djangoproject.com/en/2.1/ref/request-response/
这个即可以在后台获取传到前台也可以在前台使用{{ request.scheme }}和{{ request.get_host }}获取,我是在后台获取传到前台的
我改变了方式,不在url的路径里传递参数了,因为我是异步请求,可以直接将id使用data传过去。这样就解决了所有问题。
针对于使用render返回的数据,如果在前台直接拿的话是需要转义和转成json对象的。
def index(request): """ 患者病例系统的首页 :param request: :return: """ data = list(DICT_JYJC.objects.values()) return render(request, 'index.html', {'list': data})
<script> var data = '{{ list }}'; data = data.replace(/'/g,'\"'); console.log(data); console.log(JSON.parse(data)); </script>
自己好久没用django了,也忘记了django有哪些模板标签了,使用模板标签的话,直接就可以遍历,并不需要多此一举在script里做处理。在此加一笔,记录。推荐好文。
<span class="fz-input"> <input type="text" class="input-line" style="width: 150px;text-align: center"> <select> {% for foo in list %} <option>{{ foo.VAL }}</option> {% endfor %} </select> </span>
for(int i=0;i<list1.lenght;i++){ print(list1[i]); print(list2[i]); }
{% for txt in list %} {{ txt }} {%endfor%}
而此时我要遍历两个等长列表,麻烦来了,我无法直接使用forloop.count0作为列表的索引使用
1.在应用中创建templatetags文件夹,该文件夹下要包含一个_init_.py文件和一个存放你自定义模板方法的地方:
图中:我app名字为survey,存放我自定义模板方法的地方为survey_dealfor.py。
from django import template register = template.Library() @register.filter(name='dfor') def dfor(index, list): print(index) print(list) return list[index]
dfor方法传来两个参数,一个是索引,一个是列表,返回该列表在该索引处的值。
TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], 'libraries': { 'survey_dealfor': 'survey.templatetags.survey_dealfor', } }, }, ]
添加libraries配置项,survey_dealfor就是你马上即将要在html中load的名字,该名字对应着你自定义模板的路径。
{% for litext in item.selection %} <span> <a href="#question2" class="question-two-picture"> <span class="picture-text">{{litext}}</span> <img class="img-content" src="/static/survey/images/{{ forloop.counter0|dfor:item.selectImg }}"> </a> </span> {% endfor %}
这样就ok了,然后你一定要重启一次服务才行。这样我实现了我想要的效果,没毛病。
https://stackoverflow.com/questions/40686201/django-1-10-1-my-templatetag-is-not-a-registered-tag-library-must-be-one-of/42881074
由于django自带的模板标签语言和artTemplate有差异,如果直接在script里嵌入artTemplatedjango会不能识别而报错,要在django中使用artTemplate要加以下条件:
<script id="temptab" type="text/html"> {% verbatim %} {{ each list as v i }} <tr id="'{{ v.id }}'"> <td> {{if v.is_files}} <a href="javascript:;" onclick="dir_detail('{{ v.id }}',1)"> <span class="glyphicon glyphicon-folder-close icon-dir" aria-hidden="true"></span> <span class="fename">{{ v.name }}</span> </a> {{ else }} <span class="glyphicon glyphicon-file" aria-hidden="true"></span> {{ if v.dir_rank == 2 }} <span class="fename"><a href="dcmDetail/?id={{ v.pid }}&flag=0">{{ v.name }}</a></span> {{ else }} <span class="fename"><a href="dcmDetail/?id={{ v.pid }}&flag=1">{{ v.name }}</a></span> {{ /if }} {{/if}} </td> <td class="feinfo">{{ v.info }}</td> <td>{{ v.time | dateFormat:'yyyy-MM-dd h:m:s' }}</td> <td> {{ if v.dir_rank == 0 }} <a href="javascript:;" data-toggle="modal" data-target=".edit-dir-modal" onclick="editDir('{{ v.id }}','{{ v.name }}','{{ v.info }}')"> 编辑 </a> <a href="javascript:;" data-toggle="modal" data-target=".add-dir-modal" onclick="addSecDir('{{ v.id }}','{{ v.name }}')"> 创建子目录 </a> {{ /if }} {{ if v.dir_rank == 1 }} <a href="javascript:;" data-toggle="modal" data-target=".add-file-modal" onclick="addNewFiles('{{ v.id }}','{{ v.name }}')"> 上传DCM文件 </a> <a href="javascript:;" data-toggle="modal" data-target="#splitModal" data-keyboard="false" data-backdrop="static" onclick="split('{{ v.id }}')"> 分割 </a> {{ /if }} {{ if v.dir_rank == 2 }} <a href="dcmDetail/?id={{ v.pid }}&flag=0">show</a> {{ else if v.dir_rank == -3 }} <a href="dcmDetail/?id={{ v.pid }}&flag=1">show</a> {{ /if }} {{ if v.dir_rank == 0 || v.dir_rank == 1 || v.dir_rank == -1 || v.dir_rank == -2 }} <a href="javascript:;" onclick="del('{{ v.id }}',this)"> 删除 </a> {{ /if }} </td> </tr> {{ /each }} {% endverbatim %} </script>
你要将你的artTemplate使用{% verbatim %}{% endverbatim %}包裹起来,这样django就不会编译这段代码,然后使用后台传递来的数据,将你的数据嵌入你写的模板中:
$.ajax({ type:'post', url:'dir_detail/', data:{id:id, flag:flag}, dataType:'json', success:function(data) { var list = []; for (var i = 0;i<data.length;i++) { var dt = data[i]; dt.fields.id = dt.pk; list.push(dt.fields); } var content = template('temptab',{'list':list}); $("#file_content").html(content); if(list.length != 0) { $(".pre-dir").data('id',list[0].pid); } } });
如上:使用template方法时,你必须指定一个json对象,‘list’对应模板的list,如果你直接传递list,像这样:
var content = template('temptab',list);
仔细看,你会发现使用render可以直接传递没有序列化的实体,而且他能够恰好的映射到模板上。而使用:
HttpResponse时如果是传递普通的字符串json对象,可以直接上传,但是要上传实体时就要将实体序列化后才能上传,不然就报错,说不能上传没有序列化的数据。
如上:查询数据库,安装is_files和time两个字段排序,其中is_files是降序,time是升序。即:加个负号代表是降序,默认是使用升序
select * from db where name = 'split' and pid = '909218329989'
import shutil shutil.rmtree(path)
https://www.jianshu.com/p/7c7645674ae0
{{ data|safe }}
# save 补充条件:批量插入 bd_bctj_list = [] for o in other: bd_bctj = BD_BCTJ() bd_bctj.BLID = blid bd_bctj.JLID = jlid bd_bctj.ZZID = o['zzid'] bd_bctj.ZZXXID = o['zzxxid'] bd_bctj_list.append(bd_bctj) BD_BCTJ.objects.bulk_create(bd_bctj_list)
import django from django.db import connection import logging import traceback logger = logging.getLogger("django") @staticmethod def execute(sql, params=None, _one=False, _dict=False): """ 执行一条SQL :param sql: example: 'select * from table where name = %s' :param params: sql参数 :param _one: 默认使用fetchall,若_one=True则使用fetchone :param _dict: 封装成dict形式 :return: """ try: with connection.cursor() as cursor: cursor.execute(sql, params) if _one: result = cursor.fetchone() else: result = cursor.fetchall() if _dict: col_names = [desc[0] for desc in cursor.description] result = dict(zip(col_names, result)) except django.db.DatabaseError as e: logger.error(traceback.format_exc()) raise e return result # 如果在Python中和Mysql一起使用时,可以直接用下面方式 import MySQLdb conn = getConnection(dbparams) cursor=conn.cursor(cursorclass = MySQLdb.cursors.DictCursor); vreturn=cursor.execute(sql)
from django.db import transaction 在需要使用事务的地方: if exist == False: # use transaction with transaction.atomic(): # write files to dir dest = open(file_path, 'wb+') for chunk in f.chunks(): dest.write(chunk) dest.close() # get dcm rank dcm_rank = -1 image = sitk.ReadImage(file_path) keys = image.GetMetaDataKeys() for key in keys: if key == '0020|0013': dcm_rank = image.GetMetaData(key) # write db file = createFiles( {'pid': dir_id, 'name': f.name, 'info': filesinfo, 'dir_rank': 3, 'dcm_rank': dcm_rank, 'is_files': False, 'path': dir.path + f.name}) file.save() else: ...
如上我需要将上传dcm文件和创建dcm实体插入数据库这两个需要同步进行,添加事务只需要使用:
with transaction.atomic(): #在这里写需要同步的东西
https://www.cnblogs.com/lixiang1013/p/7748156.html
pip freeze > requirements.txt
pip install -r requirements.txt
参考链接:https://www.jianshu.com/p/440c726cc516
Django要使用oracle首先要去下载对应python版本和操作系统的oracle的驱动(cx_Oracle),由于当前我使用的Python版本是3.6.6,服务器oracle版本是11g,查阅官网关于数据库支持这块,发现支持oracle11g的django版本在2.0以下,如果是2.0以上会报错【cx_Oracle.DatabaseError: ORA-02000: missing ALWAYS keyword】,2.0只支持oracle12c极其以上版本,没办法我只好更换了我的Django版本为低版本。然后我在pypi官网上翻找了cx_Oracle【https://pypi.org/project/cx-Oracle/7.2.3/#files】驱动历史版本,发现只有cx-Oracle==5.3支持我当下情况:
pip install cx-Oracle==5.3
结果报了一个奇怪的错误,没办法我只好手动的去下载上图我标记的那个文件,下载后手动安装时我又遇到了一个特别恶心的问题就是这个exe安装时会自动读取Python的安装路径,但是由于我使用的是Anaconda去管理的Python版本,所以这个exe根本读取不了我的Python安装路径,而且还不能自己手动输入自己的Python安装路径,没办法,我试图在环境变量里配置Anaconda中已经下载的python3.6.5,再次安装这个exe还是不行!没办法,我只好老老实实的去官网上在下载一个Python3.6.6安装包,在本地安装了一下并配置好环境变量,在安装那个exe文件,问题解决了。到此我也仅仅只是安装了oracle的数据库驱动。
接下来开始下载对应服务器的oracle客户端instantclient_11_2,把其中的oci.dll、oraocci11.dll、oraociei11.dll三个文件拷贝到python3.6.6环境下lib/sit-packages目录下。
下面我开始切换我项目的环境,由于之前我项目里使用的是Anaconda里的py365创建的虚拟环境,现在oracle的驱动安装在本地Python3.6.6中了,所以要将项目环境切换到刚安装的Python3.6.6里,经过一番折腾(还好项目不大),终于切换完了,我写了一个测试文件,测试一下这个cx_Oracle驱动能不能使用:
import cx_Oracle # Create your tests here. if __name__ == '__main__': '''Hello cx_Oracle示例: conn = cx_Oracle.connect("username/pwd@localhost/dbname") cur = conn.cursor() try: #cur.execute("insert into bd_hzbl values(3,1,22,'a','b','c','d','e','f','g',null,null)") #conn.commit() cur.execute('select * from bd_hzbl') data = cur.fetchall() print(data) finally: cur.close() conn.close()
还好还好,对于插入和查询都没问题,这下我就放心了,开始在我的项目里使用oracle了【在此之前我下载了oracle的客户端64位的并配置好了环境变量,因为我要连接服务器上的数据库】。
#第一步:创建基本model: python manage.py migrate #第二步:初始化自己的model python manage.py makemigrations TestModel #第二步:初始化数据库表 python manage.py migrate TsetModel
如果以上两步出现了问题,最好看看你有没有oracle客户端,有没有oracle配置环境变量。
python manage.py inspectdb 表名 > aaa.py
一路坎坷,各种百度解决问题,现在到了开始在项目里使用它了,我创建了一个简单的表单,和一个model:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- '实体' from django.db import models # 患者病例 class BD_HZBL(models.Model): ID = models.AutoField(primary_key=True) XB = models.IntegerField() # --性别 0-男 1-女 NL = models.IntegerField() # --年龄 TSSQ = models.CharField(max_length=2) # --特殊时期 ZS = models.CharField(max_length=200) # --主诉 XBS = models.CharField(max_length=200) # --现病史 JWS = models.CharField(max_length=200) # --既往史 TGJC = models.CharField(max_length=200) # --体格检查 FZJC = models.CharField(max_length=200) # --辅助检查 BZ = models.CharField(max_length=200) # --备注 CJSJ = models.DateTimeField() # --创建时间 auto_now_add=True XGSJ = models.DateTimeField() # --修改时间 auto_now=True class Meta: db_table = 'BD_HZBL'
然后请求提交表单,在处理器中将这个表单数据赋值给实体属性,然后save实体,这是一个正常的操作:
# 保存一个实体类 bd_hzbl = BD_HZBL() bd_hzbl.ID = 3 bd_hzbl.XB = 1 # sex bd_hzbl.NL = 23 # age bd_hzbl.TSSQ = 'a' # rs + ',' + br bd_hzbl.ZS = 'b' # zs bd_hzbl.XBS = 'c' # xbs bd_hzbl.JWS = 'd' # jws bd_hzbl.TGJC = 'e' # tgjc bd_hzbl.FZJC = 'f' # jcxm bd_hzbl.BZ = 'g' # jcjg bd_hzbl.CJSJ = timezone.now() bd_hzbl.XGSJ = timezone.now() bd_hzbl.save()
django.db.utils.DatabaseError: OCI-22061: invalid format text [999999999999999999999999999999999999999999999999999999999999999]
all = BD_HZBL.objects.all()
它有神奇的给我返回了三个数据【我测试cx-Oracle时插入的三个】。这明显的是说明我的实体类写的肯定有一些问题的!
到此,我彻底卡住了,网上关于这个错误的问题寥寥无几,最后我在国外的一篇文章上找到了擦边答案:https://sourceforge.net/p/cx-oracle/mailman/message/26209197/
这篇文章讲的是关于我oracle字符集编码的问题,但是最后我也尝试设置我本地客户端字符集编码了(和服务器同步),最后还是没能解决。
我开始针对我的字段进行测试,我尝试删除所有字段(并重新生成表),仅仅保留主键字段,奇迹的是我竟然save成功了!!!数据库里出现了ID字段为1的数据,这令我欣喜若狂,接下来我又开放了一个IntegerFiled字段(重生表),不幸的是又出现了那个OCI-22061的错误!我屏蔽IntegerField字段开发CharFiled又成功插入了!我赶紧跑到django1.11官网上查找有关IntegerField的说明,并没有我所期望的信息,至此我知道了使用IntegerField是不行的,但是我没有更好的办法了,官网上说能用,但是在我这不能用。无奈,我将IntergerField字段都改成了CharField,这个问题算是心有不快的勉强解决了。
官网介绍:https://django.readthedocs.io/en/latest/ref/applications.html
网友使用:http://www.cnblogs.com/xjmlove/p/10087053.html
打印两次问题:https://stackoverflow.com/questions/37441564/redefinition-of-appconfig-ready
如果你想在你的应用程序启动时执行某件事,django提供了一个很好的方式就是在你的app应用程序里的apps.py程序里重写AppConfig超类的ready()方法
# sdd/apps.py from django.apps import AppConfig from django.utils.module_loading import autodiscover_modules class SddConfig(AppConfig): name = 'sdd' def ready(self): print(1) autodiscover_modules("***.py") # 用于启动时执行某个文件,***.py和apps.py同目录下
然后启动django,并没有执行我的ready()方法,百思不得其解,去网上查找也没有相关的说明,我打断点压根就不跳到我的断点处,虽然我可以用其他方式去在django启动时执行我的任务(就是在sdd/views.py中的开始处写我的任务,或在urls中写我的任务),但是我有洁癖,感觉那样很不爽。抱着一定要解决这个问题的态度,又跑去官网查看,无意间我往上翻了翻,哦!原来不仅要重写ready()方法还要有一些其他东西要配置!!!
按照官网说的,我在我的app下(即sdd),新建了一个__init__.py文件,在其中添加一行如下:
default_app_config = 'sdd.apps.SddConfig'
然后再次启动django,问题解决,控制台打印出了两个1,为什么会打印出两个1我也大概知道:
python manage.py runserver --noreload
先读。
class ParamsMiddleWare(MiddlewareMixin): """ AOP:处理请求参数 """ def process_response(self, request, response): """ AOP:拦截response,使用AES加密返回状态码为200的结果 :param request: :return: """ if request.path.startswith('/api/'): if response.status_code == 200: resp = HttpResponse(content=encrypt_data(KEY.encode('utf-8'), response.content.decode('utf-8')), content_type='text/plain') return resp return response
如上代码所属,我在拦截器中重新定义了一个新的response返回,这一切看起来没什么问题,在django的拦截器中这么定义也很正常,而且我在postman里测试也一切都没问题,有问题的是在前端axios的全局拦截器里竟然在错误的回调函数里返回给我一个如下错误:
Failed to load resource: net::ERR_RESPONSE_HEADERS_MULTIPLE_CONTENT_LENGTH Uncaught (in promise) Error: Network Error at createError (webpack-internal:///./node_modules/axios/lib/core/createError.js:16) at XMLHttpRequest.handleError (webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:83)
这就让我很郁闷了啊,如果我不新建一个HttpResponse返回,之间用参数中的response返回,axios能拿到数据且不会报任何错误。细想这肯定是因为两个response之间有差异导致的,到底有什么差异?我在debug发现二者的_headers属性不一样,于是我尝试之间将参数里的response的_headers赋值给新建的HttpResponse对象,这导致Django无法正常返回响应。我向肯定是我的操作姿势不对,在官网API中找到了一个可以设置header的方法
resp.setdefault(header, temp) # resp.setdefault('content-type', 'application')
def __setitem__(self, header, value): header = self._convert_to_charset(header, 'ascii') value = self._convert_to_charset(value, 'latin-1', mime_encode=True) self._headers[header.lower()] = (header, value) def setdefault(self, key, value): """Set a header unless it has already been set.""" if key not in self: self[key] = value
调用setdefault没啥问题,问题出在__setitem__上,这个方法竟然将我设置的header的值组合成了(header,value),这尼玛,导致我新建的response和参数里的response不一样!