1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > Django Admin 上传多张图片并显示缩略图

Django Admin 上传多张图片并显示缩略图

时间:2023-03-16 13:31:32

相关推荐

Django Admin 上传多张图片并显示缩略图

Django Admin 上传多张图片并显示缩略图

文章目录

Django Admin 上传多张图片并显示缩略图1.效果预览2.自定义Widget3.定义模型和模型表单3.1 定义模型3.2 定义模型表单3.3 定义模型管理器3.4 数据库迁移4.Admin使用ajax上传图片并显示缩略图4.1 设置settings.py4.2 模板脚本4.2 视图函数4.3 url设置4.4 显示缩略图4.5 效果展示5. 删除缩略图6. 缩略图预览7.保存图片到数据库7.1 自定义一个Widget7.2 修改模板脚本7.3 修改模型和模型表单8.显示模型中保存的图片9. 在数据被删除的时候删除图片10.开发建议

1.效果预览

需要的python库:因为要处理图片,必须安装pillow库。

2.自定义Widget

django Admin使用的图片上传Widget是:

<input type='file'>

非常丑陋,直接使用肯定是不行的,再说我们还要展示上传之后的图片的缩略图,因此必须自定义控件。

models.py中,自定义控件的python代码如下:

class ImageInput(ClearableFileInput):template_name = "upload_multi_img/image_multi_upload.html"def render(self, name, value, attrs=None, renderer=None):context = self.get_context(name, value, attrs)template = loader.get_template(self.template_name).render(context)return mark_safe(template)

注意:这里必须继承django原来的图片上传Widget——ClearableFileInput,否则包含这个Widget嵌入的模板表单将会出现编码错误,也就是缺少图中的enctype属性,导致文件上传失败。

Widget的HTML模板代码

就是上述代码中引入的image_multi_upload.html

{% load static %}<div class="file-button" id="upload_image"style="background-image: url('{% static 'upload_multi_img/add.png' %}');background-repeat: no-repeat;background-size: 64px;background-position: center"><input type="{{ widget.type }}" name="{{ widget.name }}"{% if widget.value != None %}value="{{ widget.value|stringformat:'s' }}"{% endif %} οnchange="uploadImage(this)"id="id_imageUpload" accept="image/jpeg,image/jpg,image/png,image/gif" multiple></div><style type="text/css">/*缩略图片样式*/.selected-img {width: 160px;height: 100px;position: relative;display: inline-block;overflow: hidden;border: solid #b1c6c1 1px;border-radius: 10px;margin-right: 10px;}/*缩略图片聚焦样式*/.selected-img:hover {border: solid #25adc6 2px;}/*上传图片按钮样式*/.file-button {width: 160px;height: 100px;position: relative;display: inline-block;overflow: hidden;border: solid #b1c6c1 1px;border-radius: 10px;margin-right: 10px;}/*上传图片按钮聚焦样式*/.file-button:hover {border: solid #25adc6 2px;}.file-button input {position: absolute;top: 0;height: 100px;opacity: 0;}/*删除缩略图片icon聚焦样式*/i:hover {color: #0081C6;}/*阿里云字体图标*/@font-face {font-family: 'iconfont'; /* project id 1361777 */src: url('///t/font_1361777_ufufbwqmfpa.eot');src: url('///t/font_1361777_ufufbwqmfpa.eot?#iefix') format('embedded-opentype'),url('///t/font_1361777_ufufbwqmfpa.woff2') format('woff2'),url('///t/font_1361777_ufufbwqmfpa.woff') format('woff'),url('///t/font_1361777_ufufbwqmfpa.ttf') format('truetype'),url('///t/font_1361777_ufufbwqmfpa.svg#iconfont') format('svg');}.iconfont {font-family: "iconfont" !important;font-size: 20px;font-style: normal;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;}.icon-delete:before {content: "\e63c";}</style>

注意:里面有张背景图片,保存目录在

3.定义模型和模型表单

3.1 定义模型

models.py中定义一个数据模型

class UploadModel(models.Model):images = models.FileField('图片', upload_to="static/kaopu_shop/UploadModel")......

3.2 定义模型表单

models.py中定义上述模型的模型表单

class UploadForm(ModelForm):images = forms.FileField(label="图片", widget=ImageInput, help_text="按住ctrl多选,最多4张", required=False)class Meta:model = UploadModelfields = ['images']

注意:在ModelForm中重写了模型中的images字段,并且必须是ModelFrom类型,否则无法嵌入模型管理器。

3.3 定义模型管理器

admin.py中定义对应的模型管理器

class UploadModelAdmin(admin.ModelAdmin):form = UploadFormadmin.site.register(UploadModel, UploadModelAdmin)#注册模型和模型管理器,别忘了,否则不显示

这样就将上述定义的模型表单嵌入到adminchange/add界面展示的表单里。如图:

3.4 数据库迁移

做完上述操作之后需要进行数据库迁移,将模型写入数据库

>python manage.py makemigrations>python manage.py migrate

4.Admin使用ajax上传图片并显示缩略图

因为这里只需要上传图片并显示图片缩略图,不是保存整个admin表单,因此必须使用Ajax来实现。这里需要引入jQuery

4.1 设置settings.py

settings.py末尾中添加以下内容

# 媒体文件根路径和URLMEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/') # media即为图片上传的根路径MEDIA_URL = '/media/'# 定义主机IPHOST = "http://127.0.0.1:8000/"# 定义图片暂存目录和模型图片目录TEMP_IMAGE_SUB_DIR = "/tempimg/"# 图片暂存目录TEMP_IMAGE_DIR = MEDIA_ROOT + TEMP_IMAGE_SUB_DIR# 模型图片目录MODEL_IMAGE_SUB_DIR = "/model_images/"MODEL_IMAGE_DIR = MEDIA_ROOT + MODEL_IMAGE_SUB_DIR# 定义缩略图URL:http://127.0.0.1:8000/mdedia/tempimg/WEB_HOST_MEDIA_URL = HOST+ MEDIA_URL[1:]+ TEMP_IMAGE_SUB_DIR# 定义模型中保存的图片URL:http://127.0.0.1:8000/mdedia/model_images/MODEL_MEDIA_URL = HOST+ MEDIA_URL[1:]+ MODEL_IMAGE_SUB_DIR

4.2 模板脚本

在模板中添加ajax脚本

{#引入jQuery的CDN#}<script src="/jquery/3.4.1/jquery.min.js"></script><script type="text/javascript">//必须加这个请求头,否则ajax请求被django屏蔽,这行代码不能放进function中声明,否则获取不到请求头var csrftoken = $.cookie('csrftoken');function uploadImage(obj) {var formData = new FormData();var files = $("#id_imageUpload")[0].files;//获取模板定义的图片上传按钮的文件//如果图片数目大于4,弹出警告if (files.length > 4) {alert("最多选择4张图片!");return;}//检查图片数目和图片类型,只允许上传jpg,png,gif格式if (0 < files.length && files.length < 5) {for (i = 0; i < files.length; i++) {var ext = files[i].name.slice(files[i].name.lastIndexOf(".") + 1).toLowerCase();if ("png" == ext || "jpg" == ext || "jpeg" == ext || "gif" == ext) {formData.append(files[i].name, files[i]);}}}if (formData) {//必须加上csrftoken ,否则验证不通过,ajax请求无效$.ajax({url: '{% url 'upload_multi_img:upload_temp_images' %}',dataType: 'json',// 返回值类型 一般设置为jsontype: 'POST',headers: {"X-CSRFToken": csrftoken},//django默认拒绝post请求,必须加 csrftoken,否则请求被屏蔽processData: false, // 告诉jQuery不要去处理发送的数据contentType: false, //告诉jQuery不检查类型data: formData,async: false,//必须设置为同步模式,否则success方法没有返回值success: function (data) {//console.log(data["image_list"])//动态添加HTML元素,显示上传的图片for (i = 0; i < data["image_list"].length; i++) {//console.log(data["image_list"][i])$("#upload_image").before("<div class=\"selected-img\">\n" +" <i class=\"iconfont icon-delete\" style=\"z-index: 999;background-color:rgba(255,255,255,.8);position: absolute;right: 3px;top: 3px;\" title=\"删除图片\" οnclick=\"delete_img(this)\"></i>\n" +" <img src=\"" + data["image_list"][i] + "\" alt=\"待选图片\" style=\"width: 160px;height: 100px;border-radius: 10px;\">\n" +"</div>");}alert("上传成功", data["msg"]);},error: function (error) {alert("服务器异常");}})}return false;}</script>

注意:

必须设置csrftoken,否则请求被django视为不安全而被屏蔽。ajax必须设置为同步模式,否则没有返回值,获取不到后端返回的缩略图URL。

4.2 视图函数

首先在settings.py中添加一下:

WEB_HOST_MEDIA_URL = os.path.join('http://127.0.0.1:8000/', MEDIA_URL[1:], 'tempimg/')

然后编写视图函数

import jsonimport osimport uuidfrom django.contrib.auth.decorators import login_requiredfrom django.http import HttpResponsefrom django.shortcuts import render# Create your views here.from django.views.decorators.csrf import csrf_exemptfrom upload_image.settings import WEB_HOST_MEDIA_URL@login_required@csrf_exemptdef upload_temp_image(request):result = {}if request.method == 'POST':files = request.FILESif files:image_url_list = []for file_name in files:image_url_list.append(handle_uploaded_file(files.get(file_name))) # 处理上传文件result = {'msg': 'success', "image_list": image_url_list, }else:result = {'msg': 'failed', "image_list": []}return HttpResponse(json.dumps(result, ensure_ascii=False), content_type="application/json,charset=utf-8") # 返回json# 处理上传的文件def handle_uploaded_file(file):# 分割文件名,提取拓展名extension = os.path.splitext(file.name)# 使用uuid4重命名文件,防止重名文件相互覆盖#注意首先在项目的根目录下新建media/tempimg,或者自己使用python代码创建目录file_name = '{}{}'.format(uuid.uuid4(), extension[1])with open(TEMP_IMAGE_DIR + file_name, 'wb+') as destination:for chunk in file.chunks():#防止文件太大导致内存溢出destination.write(chunk)# 返回图片的URLreturn os.path.join(WEB_HOST_MEDIA_URL, file_name)

4.3 url设置

在应用的urls.py中声明url

from django.urls import pathfrom upload_multi_img import viewsapp_name = 'upload_multi_img'urlpatterns = [path('upload_temp_image/', views.upload_temp_image, name="upload_temp_images")]

在项目的urls.py中引入应用的url

urlpatterns = [path('admin/', admin.site.urls),path('',include('upload_multi_img.urls'),name="upload_multi_img")]

4.4 显示缩略图

完成上面的配置之后,已经能上传图片到django后端了,但是还不能显示缩略图,因为没有配置缩略图显示URL,还需进行以下配置。

在项目的urls.py中配置缩略图显示路径:

from django.conf.urls.static import staticfrom django.contrib import adminfrom django.urls import path, includefrom upload_image import settingsurlpatterns = [path('admin/', admin.site.urls),path('',include('upload_multi_img.urls'),name="upload_multi_img")]urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # 通过URL访问图片

4.5 效果展示

做完上述配置,就可以上传图片并展示缩略图了。

5. 删除缩略图

注意到目前为止,上传的图片还没有保存到我们的数据库,只是暂存在服务器上,有的时候用户上传图片之后可能需要临时更改,这时候就需要删除缩略图。在之前的模板中添加以下脚本:

//删除选中的图片,不能使用click,因为动态添加的不能绑定clickfunction delete_img(e) {$(e).closest('.selected-img').remove();}

实现点击删除图标删除使用jquery动态添加的缩略图组件:

<div class="selected-img"><i title="删除图片" class="iconfont icon-delete" style="z-index: 999;background-color:rgba(255,255,255,.8);position: absolute;right: 3px;top: 3px;" onclick="delete_img(this)"></i><img style="width: 160px;height: 100px;border-radius: 10px;" alt="待选图片" src=data["image_list"][i]></div>

注意这里只是删除了浏览器界面渲染的HTML元素,并没有删除服务器上暂存的图片,这样做的目的是减少ajax请求,提高响应速度,服务器上暂存的图片可以在保存到数据库之后全部清空,效果:

6. 缩略图预览

为了方便用户使用,缩略图还需要有查看大图的功能,在模板中添加以下脚本:

{#显示大图#}<div id="outerdiv"style="position:fixed;top:0;left:0;background:rgba(0,0,0,0.7);z-index:10000;width:100%;height:100%;display:none;"><div id="innerdiv" style="position:absolute;"><img id="bigimg" style="border:5px solid #fff;" src="" alt="大图"/></div></div>#显示大图#}<script type="text/javascript">//因为图片是动态添加的,所以不能使用选择器选择。function show_big_img(obj) {imgShow("#outerdiv", "#innerdiv", "#bigimg", obj);}function imgShow(outerdiv, innerdiv, bigimg, obj) {var src = obj.src;//获取当前点击的pimg元素中的src属性$(bigimg).attr("src", src);//设置#bigimg元素的src属性console.log("¥¥¥¥", obj.height, obj.width);var windowW = $(window).width();//获取当前窗口宽度var windowH = $(window).height();//获取当前窗口高度var realWidth = obj.naturalWidth;//获取图片真实宽度var realHeight = obj.naturalHeight;//获取图片真实高度var imgWidth, imgHeight;var scale = 0.8;//缩放尺寸,当图片真实宽度和高度大于窗口宽度和高度时进行缩放if (realHeight > windowH * scale) {//判断图片高度imgHeight = windowH * scale;//如大于窗口高度,图片高度进行缩放imgWidth = imgHeight / realHeight * realWidth;//等比例缩放宽度if (imgWidth > windowW * scale) {//如宽度扔大于窗口宽度imgWidth = windowW * scale;//再对宽度进行缩放}} else if (realWidth > windowW * scale) {//如图片高度合适,判断图片宽度imgWidth = windowW * scale;//如大于窗口宽度,图片宽度进行缩放imgHeight = imgWidth / realWidth * realHeight;//等比例缩放高度} else {//如果图片真实高度和宽度都符合要求,高宽不变imgWidth = realWidth;imgHeight = realHeight;}$(bigimg).css("width", imgWidth);//以最终的宽度对图片缩放var w = (windowW - imgWidth) / 2;//计算图片与窗口左边距var h = (windowH - imgHeight) / 2;//计算图片与窗口上边距$(innerdiv).css({"top": h, "left": w});//设置#innerdiv的top和left属性$(outerdiv).fadeIn("fast");//淡入显示#outerdiv及.pimg$(outerdiv).click(function () {//再次点击淡出消失弹出层$(this).fadeOut("fast");});}</script>

效果如下:

7.保存图片到数据库

完成上述的设置之后,还需要在用户点击djangoadmin表单的保存按钮之后将用户上传的图片保存到数据库。因为django的模型字段ImageField不支持保存多张图片,因此直接使用TextField字段保存所有上传图片的URL

7.1 自定义一个Widget

这里自定义一个Widget是为了将图片URL上传和Admin界面的表单上传合并在一起,减少Ajax请求,也是为了能使用模型表单同时处理Admin表单数据和图片列表数据,这个Widget是隐藏在页面中不显示的。

upload_img_list.html

<Textarea name="images_list" id="images_list" style="height: 200px;width: 300px;" ></Textarea>

注意:必须加上name属性,否则数据不会在表单提交的时候被提交。

python代码

class UploadImageList(TextInput):template_name = "upload_multi_img/upload_img_list.html"def render(self, name, value, attrs=None, renderer=None):context = self.get_context(name, value, attrs)template = loader.get_template(self.template_name).render(context)return mark_safe(template)

7.2 修改模板脚本

修改之前模板脚本中的function delete_img和ajax的success回调函数。

<script type="text/javascript">var image_list = [];var csrftoken = $.cookie('csrftoken');//删除选中的图片,不能使用click,因为动态添加的不能绑定clickfunction delete_img(e) {image_list.splice($.inArray($(e).closest('.selected-img').children("img").attr("src"), image_list), 1);//移除缩略图$("#images_list").val(image_list.join(','));$(e).closest('.selected-img').remove();}function uploadImage(obj) {.......if (formData) {//必须加上csrftoken ,否则验证不通过,ajax请求无效$.ajax({....success: function (data) {.....//保存返回的图片URL到列表中var index = $.inArray(data["image_list"][i], image_list);if (index < 0) {image_list.push(data["image_list"][i])}}var list = image_list.join(',');//list是以,分割的字符串$("#images_list").val(list);alert("上传成功", data["msg"]);},error: function (error) {...}})}return false;}</script>

7.3 修改模型和模型表单

class UploadModel(models.Model):images = models.FileField('图片', upload_to="static/upload_multi_img/")images_list = models.CharField('', max_length=10000)def save(self, *args, **kwargs):# 阻止images字段的数据保存在数据库中,因为我们不需要self.images = ""model_images = []print(self.images_list)# 将暂存目录中的图片转存到正式目录for root, dirs, files in os.walk(TEMP_IMAGE_DIR):print('files:', files)for file in files:if os.path.join(WEB_HOST_MEDIA_URL, file) in self.images_list:shutil.move(TEMP_IMAGE_DIR + file, MODEL_IMAGE_DIR + file)model_images.append(os.path.join(MODEL_MEDIA_URL, file))# 清空暂存目录下所有图片shutil.rmtree(TEMP_IMAGE_DIR)os.mkdir(TEMP_IMAGE_DIR)# 将模型原来的图片URL换为存到正式目录后的URLself.images_list = model_images# 必须调用父类的方法,否则数据不会保存super().save(*args, **kwargs)class UploadForm(ModelForm):images = forms.FileField(label="图片", widget=ImageInput, help_text="按住ctrl多选,最多4张", required=False)images_list = forms.CharField(label='', widget=UploadImageList, help_text='', required=False)class Meta:model = UploadModelfields = ['images', 'images_list']

注意:images_listlabel参数要保持空值,否则会渲染出一个label标签。

经过上面的修改,就能把图片保存到数据库中了,效果:

8.显示模型中保存的图片

上面已经把图片URL以字符串的形式保存在数据库中了,接下来还需要把保存的图片显示在admin界面上。

修改upload_img_list.html为:

<Textarea name="images_list" id="images_list"style="height: 200px;width: 300px;" hidden>{{ widget.value|stringformat:'s' }}</Textarea>

image_multi_upload.html再添加一个脚本

{#显示模型中保存的图片#}<script type="text/javascript">$(document).ready(function () {var model_image_list = $("#images_list").val().split(",")//如果$("#images_list").val()为空,会返回一个"None"字符串if (model_image_list.length > 1) {for (i = 0; i < model_image_list.length; i++) {$("#upload_image").before("<div class=\"selected-img\">\n" +" <i class=\"iconfont icon-delete\" style=\"z-index: 999;background-color:rgba(255,255,255,.8);position: absolute;right: 3px;top: 3px;\" title=\"删除图片\" οnclick=\"delete_img(this)\"></i>\n" +" <img src=\"" + model_image_list[i].replace("'", '').replace("'", '') + "\" alt=\"待选图片\" style=\"width: 160px;height: 100px;border-radius: 10px;\" οnclick=\"show_big_img(this)\">\n" +"</div>");}}})</script>

这样就能显示模型中保存的图片了。效果:

9. 在数据被删除的时候删除图片

最后,还需要在数据被删除时删除保存的图片,防止产生大量的垃圾图片。在models.py中添加一个信号接收器,接收模型被删除的信号:

# 删除被删除的模型的图片@receiver(post_delete, sender=UploadModel)def delete_upload_files(sender, instance, **kwargs):image_list = getattr(instance, 'images_list', '')if not image_list:returnelse:# 去除image_list中URL存在的''字符list = image_list.replace("'", "").replace("'", "").split(",")# 删除被删除的模型的图片for image in list:# 获取文件名delete_image_name = image.split('/')[-1]os.remove(MODEL_IMAGE_DIR + delete_image_name)class UploadForm(ModelForm):.......

注意:这里使用信号接收器而不是重写模型delete方法是因为批量删除模型的时候不会调用模型的delete方法。

10.开发建议

django是一个非常强大的框架,但是唯一不足的地方是它的Admin界面非常丑陋,本文通过一系列定制在Admin实现了多张图片上传,显示缩略图,缩略图预览,保存多张图片到数据库的功能。按照这样的方式还可以做一定的拓展,比如上传文件时显示相应的文件图标等。

完整源码链接:链接: /s/1Sa8cEg19bNqoDkiCGEWaog 提取码: fb49

注意:python版本是python3.6.8,django版本是2.2.6,开发工具是pycharm

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。