django 文件、头像上传+相关setting配置

2019-04-15 15:39发布

本文梳理: 1、相关概念 2、setting相关配置 3、功能实现 
  • 单文件上传
  • 多文件同时上传
  • 头像上传
  • 文件,头像等 与 数据库表关联,实现增删改查功能
相关概念
  • 媒体文件:用户上传的文件
  • 静态文件:css,js,image等
  • 开发环境:使用django内置服务器处理静态文件
  • 生产环境:使用apache2/nginx服务器处理静态文件映射
*在生成环境中,集中存放静态资源有利于使用Lighttpd/Nginx/apache2托管静态资源   二、setting相关配置 静态文件 Static files # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.1/howto/static-files/ STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR,'static') STATICFILES_DIRS = ( # 可以不用,都放在 app 里的 static 中也可以 # os.path.join(BASE_DIR, "lib"), # '/static/', # 用不到的时候可以不写这一行 ) 用户上传文件: # upload folder MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') STATICFILES_FINDERS = ( "django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder" ) 两项配置的区别: MEDIA_XXX配置项用来管理媒体文件。经常由FileFields字段上传,它们被保存在settings.MEDIA_ROOT指定的目录下,通过settings.MEDIA_URL指定的路径访问。 STATIC_XXX配置项用来管理静态文件。它们通过manage.py collectstatic命令汇集到settings.STATIC_ROOT目录,并通过settings.STATIC_URL指定的路径访问。 三、功能实现 : ajaxFileUpload 1.上传1个文件,同时根据日期自动创建文件夹存储
// 附件上传 function uploadFile(){ $.ajaxFileUpload({ url : "/uploadFile/", secureuri : false, // 一般设置为false fileElementId : "file", // 文件上传表单的id // async : false, type:'post', processData: false, // tell jquery not to process the data contentType: false, // tell jquery not to set contentType dataType : 'json', // 返回值类型 一般设置为json success : function(ret) { // 服务器成功响应处理函数 layer.msg(ret.retMsg); }, error: function(data, status, e) { //提交失败自动执行的处理函数。 layer.msg(e); } }); } # 文件上传 def uploadFile(request): if request.method == 'POST': try: BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) file_obj = request.FILES.get('file', None) if not file_obj: ret = { 'success': False, 'retCode': 500, 'retMsg': "no files for upload!" } return HttpResponse(json.dumps(ret), content_type='application/json') dateTime = time.strftime('%Y%m%d',time.localtime(time.time())) if not os.path.exists(BASE_DIR+'/media/files/'+dateTime): os.mkdir(BASE_DIR + '/media/files/' + dateTime) f = open(os.path.join(BASE_DIR, 'media', 'files', dateTime, file_obj.name), 'wb') for chunk in file_obj.chunks(): f.write(chunk) f.close() ret = { 'success': True, 'retCode': 200, 'retMsg': "附件上传成功!" } return HttpResponse(json.dumps(ret), content_type='application/json') except Exception as e: logger.error('附件上传失败!错误原因:', str(e)) ret = { 'success': False, 'retCode': 200, 'retMsg': "附件上传失败!错误原因:"+str(e) } return HttpResponse(json.dumps(ret), content_type='application/json') 动态创建文件夹: https://blog.csdn.net/wang010366/article/details/52512516/ dateTime = time.strftime('%Y%m%d',time.localtime(time.time())) if not os.path.exists(BASE_DIR+'/media/files/'+dateTime): os.mkdir(BASE_DIR + '/media/files/' + dateTime) f = open(os.path.join(BASE_DIR, 'media', 'files', dateTime, file_obj.name), 'wb') 2.一次上传多个附件
file_obj = request.FILES.getlist('moreFile') print (file_obj) if not file_obj: ret = { 'success': False, 'retCode': 500, 'retMsg': "no files for upload!" } return HttpResponse(json.dumps(ret), content_type='application/json') dateTime = time.strftime('%Y%m%d',time.localtime(time.time())) if not os.path.exists(BASE_DIR+'/media/files/'+dateTime): os.mkdir(BASE_DIR + '/media/files/' + dateTime) for file in file_obj: f = open(os.path.join(BASE_DIR, 'media', 'files', dateTime, file.name), 'wb') for chunk in file.chunks(): f.write(chunk) f.close() 3.图书封面上传 (头像上传) 1.所谓的上传到数据库,不是讲图片本身或者二进制码放在数据库,实际上也是将文件上传到服务器本地,数据 库只是存了一个文件的路径,这样用户要调用文件的时候就可以通过路径去服务器指定的位置找了 2.创建ORM的时候,avatar字段要有一个upload_to=''的属性,指定上传后的文件放在哪里 3.往数据库添加的时候,文件字段属性赋值跟普通字段在形式上是一样的,如: models.User.objects.create(username=name,avatar=avatar) 4.如果有两个用户上传的文件名重复,系统会自动将文件改名 ① 方法1 - 根绝userId创建文件夹存入照片,新的照片上传时文件夹有则替换,无则新增 book.html
图书封面
book.js / 图书封面 上传 function uploadBookCover(obj){ var file_obj = $(obj)[0].files[0]; if (!file_obj.type.match('image.*')) { layer.msg("请选择符合格式的图片"); return; } // 浏览器兼容处理 实现图片预览功能 if(window.URL.createObjectURL){ // 谷歌浏览器 var v = window.URL.createObjectURL(file_obj); //传obj这个文件对象,相当于把这个文件上传到了浏览器,这个时候就可以预览了 $('.book_cover img').attr('src',v);//找到img标签,把src修改后就可以访问了,但是对于浏览器有兼容性 // window.URL.revokeObjectURL(v); // 手动清除内存中,要想手动,需要加载完毕再去释放#} $('.book_cover img').load(function () { window.URL.revokeObjectURL(v); }) }else if(window.FileReader){ var file_Read = new FileReader(); file_Read.onload = function (){ $(".book_cover img").attr('src',this.result); }; file_Read.readAsDataURL(file_obj); }else{ console.log(3333) } $.ajaxFileUpload({ url : "/uploadBookCover/", secureuri : false, // 一般设置为false fileElementId : "bookCover", // 文件上传表单input的id // async : false, type:'post', processData: false, // tell jquery not to process the data contentType: false, // tell jquery not to set contentType dataType : 'json', // 返回值类型 一般设置为json success : function(ret) { // 服务器成功响应处理函数 layer.msg(ret.retMsg); }, error: function(data, status, e) { //提交失败自动执行的处理函数。 layer.msg(e); } }); } book.py # 图书封面上传 def uploadBookCover(request): root_path = 'media/img' if request.method == 'POST': bookCover = request.FILES.get('bookCover') userId = request.user.id path = '%s/%s_bookCover'%(root_path,userId) # 设定图片路径 bookRet = handleUpdateBookCover(bookCover,path,userId) # 获得bookRet,handleUpdateBookCover处理 if bookRet: request.user.bookCover = '%s/%s' % (path,bookRet[1]) request.user.save() ret = { 'success': True, 'retCode': 200, 'retMsg': "图书封面上传成功!" } return HttpResponse(json.dumps(ret), content_type='application/json') ret = { 'success': False, 'retCode': 200, 'retMsg': "图书封面上传失败!" } return HttpResponse(json.dumps(ret), content_type='application/json') def handleUpdateBookCover(bookCover,path,userId): """ return bookCover full path if success """ if bookCover.size>=5*1024*1024: return false bookCover_type_name_path = handleUpdateBookCoverFinally(bookCover) # 对头像进行处理,返回一个List if not bookCover_type_name_path: return false try: if not os.path.exists(path): # 假设路径不存在,创建目录 os.mkdir(path) # bookCover_type_name_path = ['jpeg','154546445.54454','media/img/bookCover/154546445.54454'] temp_path_fixed_name = 'media/img/bookCover/%s.%s'% (bookCover_type_name_path[1],bookCover_type_name_path[0]) #对文件重命名 os.rename(bookCover_type_name_path[2],temp_path_fixed_name) listdir = os.listdir(path) if listdir: # 移除原先头像 os.remove(path+'/'+listdir[0]) shutil.move(temp_path_fixed_name,path) # 将新文件拷贝过去 finally_book_name = bookCover_type_name_path[1]+"."+bookCover_type_name_path[0] return "media/img/%s_bookCover/%s.%s" % (userId,bookCover_type_name_path[1],bookCover_type_name_path[0]),finally_book_name except Exception as e: logger.error("上传图书封面报错:", str(e)) ret = { 'success': False, 'retCode': 200, 'retMsg': "图书封面上传失败!错误原因:" + str(e) } return HttpResponse(json.dumps(ret), content_type='application/json') def handleUpdateBookCoverFinally(bookCover): temp_name = str(time.time() + random.randint(1,500)) temp_path = 'media/img/bookCover/%s' % temp_name temp_file = open(temp_path,'wb') for chunk in bookCover.chunks(): temp_file.write(chunk) # 将头像上传到这个路径 temp_file.close() img_type = imghdr.what(temp_path) # 获取上传图片类型例如jpg,png等 if not img_type: # 假如不是图片类型文件,移除文件并报错 os.remove(temp_path) return false return img_type,temp_name,temp_path     ② 方法2 html,js 等同于book   上传照片就新增一张,图片名随机数方法生成 common.py # 工具包 import uuid,hashlib # 随机数使用 class utils(): def getRandomStr(self): # 获取uuid的随机数 uuid_val = uuid.uuid4() # 获取uuid的随机数字符串 uuid_str = str(uuid_val).encode('utf-8') # 获取md5实例 md5 = hashlib.md5() # 拿取uuid的md5摘要 md5.update(uuid_str) # 返回固定长度的字符串 return md5.hexdigest() author.py def uploadHeadimg(request): user = request.user if request.method == "GET": data = { 'username': user.username, # 拿头像文件路径 'icon': '/media/uploads/icons/hehe.jpg' } return HttpResponse(json.dumps(data), content_type='application/json') else: # 拿文件数据 icon = request.FILES.get('headimg') # 获取图片的随机名 file_name = 'uploads/icons/' + utils().getRandomStr() + '.jpg' # 拼接一个自己的文件路径 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) image_path = os.path.join('media', file_name) # 打开拼接的文件路径 with open(image_path, 'wb')as fp: # 遍历图片的块数据(切块的传图片) for i in icon.chunks(): # 将图片数据写入自己的那个文件 fp.write(i) # 拼接返回数据 data = { 'username': user.username, # 拿头像文件路径 'icon': '/media/' + file_name } return HttpResponse(json.dumps(data), content_type='application/json')   4、文件、头像与数据库单条数据绑定,实现增删改查功能 ①图书封面(头像)数据绑定 for li in list: allList.append({ "id": li.id, "head_img": li.head_img, 1.TypeError: Object of type 'ImageFieldFile' is not JSON serializable 解决办法: str(li.head_img)  转成字符串   头像上传前期准备:搭建文件服务器 (django3.7) from django.views.static import serve from djangoPro.settings import MEDIA_ROOT pageurlpatterns = [ path('media/(?P.*)', serve, {"document_root": MEDIA_ROOT}), ] 问题描述:django3.7版本 path路径配置不支持正则写法 WARNINGS: ?: (2_0.W001) Your URL pattern 'media/(?P.*)' has a route that contains '(?P<', begins with a '^', or ends with a '$'. This was likely an oversight when migrating to django.urls.path(). 解决办法: from django.urls import path,re_path from django.views.static import serve from djangoPro.settings import MEDIA_ROOT pageurlpatterns = [ re_path(r'^media/(?P.*)$', serve, {"document_root": settings.MEDIA_ROOT}), ] 功能思路: 1、前端图片预览 2、点击保存时,将此图片地址存入数据库对应字段 3、获取当前数据原图片地址,检测是否存在此文件,有则删除,无则添加 4、页面刷新,查询数据库,获取此条数据图像字段值(图片服务器地址)展示图片   功能实现(以修改为例): author.html
作者头像
common.js // 图片预览 function imagePreview(obj){ var file_obj = $(obj)[0].files[0]; // 浏览器兼容处理 实现图片预览功能 if(window.URL.createObjectURL){ var v = window.URL.createObjectURL(file_obj); // 传obj这个文件对象,相当于把这个文件上传到了浏览器,这个时候就可以预览了 $('.img_box img').attr('src',v); // 找到img标签,把src修改后就可以访问了,但是对于浏览器有兼容性 // window.URL.revokeObjectURL(v); // 手动清除内存中,要想手动,需要加载完毕再去释放#} $('.img_box img').load(function () { window.URL.revokeObjectURL(v); }) }else if(window.FileReader){ var file_Read = new FileReader(); file_Read.onload = function (){ $(".img_box img").attr('src',this.result); }; file_Read.readAsDataURL(file_obj); }else{ console.log("图片预览失败") } } author.js function edit(){ if(!$('#form').form('enableValidation').form('validate')) return; var formData = new FormData(); formData.append("headimg", $("#headimg")[0].files[0]); formData.append('csrfmiddlewaretoken', $("#csrf_token").val()); $("#form").serializeArray().forEach(function(item,i){ formData.append(item.name , item.value) }); $.ajax({ type: 'post', url: '/modifyAuthor/', data: formData, processData: false, // tell jquery not to process the data contentType: false, success: function(data){ layer.msg(data.retMsg); if(data.success){ $('#dlg').dialog('close'); $('#dg').datagrid('reload'); } } }); } author.py def modifyAuthor(request): id = request.POST.get("modifyId") if request.FILES.get("headimg"): head_img = utils.headimgHandle(request, request.FILES.get("headimg"), sql, id) models.Author.objects.filter(id=id).update(head_img=head_img) else: models.Author.objects.filter(id=id).update() ret = { 'success': True, 'retCode': 200, 'retMsg': "Author修改成功!" } return HttpResponse(json.dumps(ret), content_type='application/json') common.py # 工具包 class utils(): def headimgHandle(self, icon, sql, id): try: file_name = '%s.jpg' % str(int(time.time())) # 拼接一个自己的文件路径 image_path = os.path.join('media/icons/', file_name) # 打开拼接的文件路径 with open(image_path, 'wb')as fp: # 遍历图片的块数据(切块的传图片) for i in icon.chunks(): # 将图片数据写入自己的那个文件 fp.write(i) if id: imgOldPath = str(sql.objects.get(id=id).head_img)[1:] try : # 假如c存在原图片,移除 f = open(imgOldPath) f.close() os.remove(imgOldPath) except Exception as e: logging.info (str(e)) return "/%s" % image_path except Exception as e: logging.error("图片处理失败:%s" % str(e)) return '' ②附件,纯文件数据绑定 若是单个附件的上传可以按照图片上传来写 多个附件上传(手动上传,删除,不存在自动替换)可以在单独建一张文件表,单独保存   相关网址: https://www.cnblogs.com/starof/p/4682812.html