首先,我的所有附件、图片等都是保存到单独的表里面的,所以我这里有一张表。
from .BaseModel import BaseModel
from django.db import models
class Attachment(BaseModel):
STATUS = (
(0, '正常'),
(9, '禁用'),
)
original_name = models.CharField(max_length=255, verbose_name = "原始名称")
name = models.CharField(max_length=255, verbose_name = "新名称")
url = models.CharField(max_length=255, verbose_name = "图片存放地址")
status = models.SmallIntegerField(choices = STATUS, db_index = True, verbose_name = "状态")
class Meta:
db_table = 'attachment'
之前也提到怎么自定义Widget
https://blog.csdn.net/tang05709/article/details/104442856
原理一样
class TyFileInput(Input):
template_name = '../templates/widgets/file.html'
file.html
使用
from .BaseForm import BootstrapModelForm
from backend.widgets.TyWidgets import TyFileInput
from common.models import FriendLink
class FriendLinkForm(BootstrapModelForm):
class Meta:
model = FriendLink
fields = ['name', 'url', 'logo']
widgets = {
"logo":TyFileInput(attrs={'class':'customer-form-file media-picker-button', 'data-upload-path': 'common', 'id': 'logo_uploader', 'data-multiple': 'multiple'}),
}
说明下attrs
media-picker-button: 用于查找当前button。
data-upload-path: 用于指定上传的目录。
data-multiple:用于指定是否多文件上传,不写表示单文件上传。
id: 覆盖生成的id,用于plupload的点击事件。
后端上传图片
import json, time, uuid, os.path
from django.http import HttpResponse
from django.views.generic.base import View
from common.models.Attachment import Attachment
class UploadFileView(View):
resp = {'errorcode': 100, 'data': '请选择图片'}
def post(self, request, *args, **kwargs):
upfile = request.FILES.get('image', None)
updir = request.POST.get('updir', '')
if(upfile == None):
return HttpResponse(json.dumps(resp), content_type="application/json")
new_file_name = self._hash_filename(upfile.name)
res_save_path = self._get_path(updir)
save_path = res_save_path['save_path']
local_save_path = res_save_path['local_save_path']
local_save_file = local_save_path + new_file_name
save_file = save_path + new_file_name
url = 'http://127.0.0.1:8000/' + save_file
with open(local_save_file, 'wb') as f:
for line in upfile.chunks():
f.write(line)
f.close()
model = Attachment()
model.original_name = upfile.name
model.name = new_file_name
model.url = save_file
model.status = 0
model.save()
resp = {'errorcode': 200, 'id': model.id, 'url': url}
return HttpResponse(json.dumps(resp), content_type="application/json")
def _hash_filename(self, filename):
_, suffix = os.path.splitext(filename)
return '%s%s' % (uuid.uuid4().hex, suffix)
def _get_path(self, updir):
if(updir == ''):
path = 'static/images/' + time.strftime("%Y%m%d", time.localtime()) + '/'
else:
path = 'static/images/' + updir + '/' + time.strftime("%Y%m%d", time.localtime()) + '/'
# 本地储存路径
local_save_path = os.path.join('frontend', path)
# 数据库存储路径
save_path = os.path.join(path)
isExists = os.path.exists(local_save_path)
if not isExists:
os.makedirs(local_save_path)
return {'save_path': save_path, 'local_save_path': local_save_path}
由于是本地开发,这里有个目录问题没处理,后续再处理。
前端js
g_object_ids = new Array(); // 多文件上传图片返回的id
g_object_urls = new Array(); // 多文件上传图片返回的url
$('.media-picker').each(function() {
var el = $(this);
var elbtn = el.find('.media-picker-button');
var multi_selection = false;
// 上传目录
var inputField = el.find('input[type=hidden]');
var token = $.cookie('csrftoken');
// 是否多文件上传
if (elbtn.attr('data-multiple') == 'multiple') {
multi_selection = true;
}
var updir = elbtn.attr('data-upload-path');
var uploader = new plupload.Uploader({
runtimes : 'html5,flash,silverlight,html4',
browse_button : elbtn.attr('data-id') + '_uploader',
url : '/backend/upload',
file_data_name: 'image',
multi_selection: multi_selection,
multipart_params: { 'updir': updir, 'csrfmiddlewaretoken': token },
auto_start: true,
flash_swf_url : '../plugins/plupload/Moxie.swf',
silverlight_xap_url : '../plugins/plupload/Moxie.xap',
filters : {
max_file_size : '10mb',
prevent_duplicates: false,
mime_types: [
{title : "Image files", extensions : "jpg,gif,png"},
{title : "Video files", extensions : "mp4"}
]
},
init: {
FilesAdded: function(up, files) {
up.start(); //选择完后直接上传
},
FileUploaded: function(up, file, info) {
if (info.status == 200) {
var file_infos = $.parseJSON(info.response);
if(file_infos.errorcode == 200) {
var file_url = file_infos.url;
var file_id = file_infos.id;
var imageHtml = '';
if (multi_selection) {
g_object_urls.push(file_url);
g_object_ids.push(file_id)
inputField.val(JSON.stringify(g_object_ids));
for(var i = 0; i< g_object_ids.length; i++) {
imageHtml += ' X ';
}
} else {
inputField.val(file_id);
var imageHtml = '';
imageHtml = ' X ';
}
el.find('.media-list ul').html(imageHtml);
} else {
alert(file_infos.data)
}
} else {
alert(info.response);
}
},
Error: function(up, err) {
console.log(err.code)
console.log(err.message)
}
}
});
uploader.init();
})
$('.media-picker').each(function() {
var el = $(this)
var inputField = el.find('input[type=hidden]');
var elbtn = el.find('.media-picker-button');
var multi_selection = false;
// 是否多文件上传
if (elbtn.attr('data-multiple') == 'multiple') {
multi_selection = true;
}
if(multi_selection) {
el.on('click', '.delete-image', function() {
// 后台不做删除,可在后台禁用
var currentId = $(this).attr('data-id');
var currentUrl = $(this).attr('data-url');
// 删除当前的父级li
$(this).parent().remove();
// 重新赋值数组
// 去掉url数组中的当前url
for(var i in g_object_urls) {
if(g_object_urls[i] == currentUrl) {
g_object_urls.splice(i, 1);
break;
}
}
// 去掉id数组中的当前id
for(var i in g_object_ids) {
if(g_object_ids[i] == currentId) {
g_object_ids.splice(i, 1);
break;
}
}
inputField.val(JSON.stringify(g_object_ids));
})
} else {
el.on('click', '.delete-image', function() {
// 显示值为空, 后台不做删除,可在后台禁用
el.find('.media-list ul').html('');
inputField.val('');
})
}
})
更新:
在file.html添加显示图片的变量。
{%if media_list%}
{%for img in media_list%}
{%endfor%}
{%endif%}
自定义TyFileInput中需要把变量传递给模板
def __init__(self, attrs=None, media_list=None):
super().__init__(attrs)
self.media_list = media_list
template_name = '../templates/widgets/file.html'
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
if 'media_list' not in context:
context['media_list'] = self.media_list
return context
使用自定义TyFileInput
class ProductForm(BootstrapModelForm):
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
images = []
if self.instance.image:
images = [self.instance.image.url]
self.fields['image'] = fields.CharField(label = '图片', widget = TyFileInput(attrs={'class': "customer-form-file media-picker-button", 'data-upload-path': 'product', 'id': 'image_uploader'}, media_list = images))
class Meta:
model = Product
fields = ['title', 'category', 'price', 'image', 'photos', 'seo_title', 'seo_keywords', 'seo_description', 'content', 'status']
widgets = {
"seo_description": widget.Textarea(attrs={'class':'form-control', 'rows': 5}),
"status":TyRadioSelect(attrs={'class':'customer-form-radio'}),
"content": widget.Textarea(attrs={'class':'form-control', 'rows': 5}),
}
这样就可以把图片显示出来了。
plupload也支持多图片上传,以Product为例,
image = models.OneToOneField(Attachment, db_column = "image", related_name = "image", on_delete = models.DO_NOTHING, null = True, blank=True, verbose_name = "图片")
photos = models.ManyToManyField(Attachment, through='ProductPhoto', through_fields=('product', 'photo'), related_name = "photos", blank=True, verbose_name = "画册")
由于上传后保存的是json数据,所以上传后需要处理。
def clean_photos(self):
photos = self.cleaned_data['photos']
clean_photos = []
if photos != '':
photos_arr = json.loads(photos)
for photo in photos_arr:
attachment = Attachment.objects.get(id=photo)
clean_photos.append(attachment)
return clean_photos
同样编辑的时候,也需要处理id和图片
处理图片:
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
photos = []
if not self.instance._state.adding and self.instance.photos.all():
for photo in self.instance.photos.all():
photos.append(photo.url)
self.fields['photos'] = fields.CharField(label = '画册', widget = TyFileInput(attrs={'class': "customer-form-file media-picker-button", 'data-upload-path': 'product', 'data-multiple': 'multiple', 'id': 'photos_uploader'}, media_list = photos))
处理id
def format_value(self, value):
if isinstance(value, list):
photo_ids = ''
if len(value) > 0:
photo_ids_list = []
for val in value:
photo_ids_list.append(val.id)
photo_ids = json.dumps(photo_ids_list)
return photo_ids
else:
value = super().format_value(value)
return str(value)
全部代码如下:
model
from .BaseModel import BaseModel
from django.db import models
from django.urls import reverse
from .Category import Category
from .Attachment import Attachment
class Product(BaseModel):
STATUS = [
[0, '正常'],
[1, '推荐'],
[2, '置顶'],
[9, '禁用'],
]
category = models.ForeignKey(Category, on_delete = models.DO_NOTHING, verbose_name = "栏目")
title = models.CharField(max_length=255, verbose_name = "标题")
status = models.SmallIntegerField(default = 0, choices = STATUS, db_index = True, verbose_name = "状态")
price = models.DecimalField(max_digits = 11, decimal_places = 2, default = 0, verbose_name = "价格")
seo_title = models.CharField(max_length=255, null=True, blank=True, verbose_name = "seo标题")
seo_keywords = models.CharField(max_length=255, null=True, blank=True, verbose_name = "seo关键字")
seo_description = models.CharField(max_length=255, null=True, blank=True, verbose_name = "seo描述")
click = models.IntegerField(default = 0, verbose_name = "点击量")
sort = models.IntegerField(default = 0, verbose_name = "排序")
image = models.OneToOneField(Attachment, db_column = "image", related_name = "image", on_delete = models.DO_NOTHING, null = True, blank=True, verbose_name = "图片")
photos = models.ManyToManyField(Attachment, through='ProductPhoto', through_fields=('product', 'photo'), related_name = "photos", blank=True, verbose_name = "画册") #symmetrical = False
content = models.TextField(null=True, blank=True, verbose_name = "详情")
def get_absolute_url(self):
return reverse('backend:product-index')
# 调用时返回自身的属性,不然都是显示xx object
def __str__(self):
return self.title
class Meta:
db_table = 'product'
多图片关系model:
from .BaseModel import BaseModel
from django.db import models
from .Product import Product
from .Attachment import Attachment
class ProductPhoto(BaseModel):
product = models.ForeignKey(Product, on_delete = models.DO_NOTHING, verbose_name = "产品")
photo = models.ForeignKey(Attachment, on_delete = models.DO_NOTHING, verbose_name = "图片")
class Meta:
db_table = 'product_photo'
modelform:
from .BaseForm import BootstrapModelForm
from django.forms import widgets as widget
from django.forms import fields
from backend.widgets.TyWidgets import TyRadioSelect, TyFileInput
from common.models import Product, Attachment
import json
class ProductForm(BootstrapModelForm):
def __init__(self, *args, **kwargs):
super(ProductForm, self).__init__(*args, **kwargs)
images = []
if self.instance.image:
images = [self.instance.image.url]
self.fields['image'] = fields.CharField(label = '图片', widget = TyFileInput(attrs={'class': "customer-form-file media-picker-button", 'data-upload-path': 'product', 'id': 'image_uploader'}, media_list = images))
photos = []
if not self.instance._state.adding and self.instance.photos.all():
for photo in self.instance.photos.all():
photos.append(photo.url)
self.fields['photos'] = fields.CharField(label = '画册', widget = TyFileInput(attrs={'class': "customer-form-file media-picker-button", 'data-upload-path': 'product', 'data-multiple': 'multiple', 'id': 'photos_uploader'}, media_list = photos))
def clean_photos(self):
photos = self.cleaned_data['photos']
clean_photos = []
if photos != '':
photos_arr = json.loads(photos)
for photo in photos_arr:
attachment = Attachment.objects.get(id=photo)
clean_photos.append(attachment)
return clean_photos
class Meta:
model = Product
fields = ['title', 'category', 'price', 'image', 'photos', 'seo_title', 'seo_keywords', 'seo_description', 'content', 'status']
widgets = {
"seo_description": widget.Textarea(attrs={'class':'form-control', 'rows': 5}),
"status":TyRadioSelect(attrs={'class':'customer-form-radio'}),
"content": widget.Textarea(attrs={'class':'form-control', 'rows': 5}),
}
view:
from django.shortcuts import redirect, render
from django.views.generic.list import ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from common.models.Product import Product
from common.models.ProductPhoto import ProductPhoto
from backend.forms.ProductForm import ProductForm
class ProductIndexView(ListView):
model = Product # 指定模型
context_object_name = 'grid' # 默认object_list
paginate_by = 2 # 每页显示数量 默认Paginator实例 page_obj
ordering = ['-id'] # 默认排序
template_name = 'product/index.html'
class ProductCreateView(CreateView):
model = Product # 指定模型
template_name = 'product/create.html'
form_class = ProductForm
class ProductUpdateView(UpdateView):
model = Product # 指定模型
template_name = 'product/update.html'
form_class = ProductForm
#def get(self, request, *args, **kwargs):
# adv_positin = Product.objects.get(id = self.kwargs['pk'])
# #form = self.form_class(instance=adv_positin)
# form = ProductForm(instance=adv_positin)
# return render(request, self.template_name, {'form': form})
class ProductDeleteView(DeleteView):
#model = Product
#success_url = '/backend/product/index'
def post(self, request, *args, **kwargs):
adv_position = Product.objects.get(id = self.kwargs['pk'])
adv_position.status = 0 if adv_position.status == 8 else 8
adv_position.save()
return redirect('/backend/product/index')
template:
产品
列表
自定义plupload上传
from django.forms.widgets import RadioSelect, Input
import json
class TyRadioSelect(RadioSelect):
template_name = '../templates/widgets/radio.html'
option_template_name = '../templates/widgets/radio_option.html'
class TyFileInput(Input):
media_list = None
def __init__(self, attrs=None, media_list=None):
super().__init__(attrs)
self.media_list = media_list
template_name = '../templates/widgets/file.html'
def format_value(self, value):
if isinstance(value, list):
photo_ids = ''
if len(value) > 0:
photo_ids_list = []
for val in value:
photo_ids_list.append(val.id)
photo_ids = json.dumps(photo_ids_list)
return photo_ids
else:
value = super().format_value(value)
return str(value)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
if 'media_list' not in context:
context['media_list'] = self.media_list
return context
class TyEditorInput(Input):
template_name = '../templates/widgets/editor.html'
上传图片template:
{% load static %}