Django的ModelForm自定义FileInput结合plupload实现ajax上传图片

首先,我的所有附件、图片等都是保存到单独的表里面的,所以我这里有一张表。

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}),
            }  

    这样就可以把图片显示出来了。

    Django的ModelForm自定义FileInput结合plupload实现ajax上传图片_第1张图片

    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)

     

    Django的ModelForm自定义FileInput结合plupload实现ajax上传图片_第2张图片

     

    全部代码如下:

    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:

    产品

    列表
    {% csrf_token %}
    {{ form.as_div }}

     

    自定义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 %}
    
      {%if media_list%} {%for img in media_list%}
    • {%endfor%} {%endif%}

     

    你可能感兴趣的:(python)