python-django如何在sae中使用自带ImageField和FileField

从大学毕业后,就开始使用python。最开始接触的是zope/plone系统,由于系统架构较为复杂,学习也付出不少代价,但是也累积了不少经验。后来转为django开发,一用就是三年,从此以后爱上django这个框架。当然中间我也了解了不少python其他的框架,pyramid也是很优秀的一款。
         进入正题,我想带有复杂工作流的cms框架(暂且叫做zcmsn)部署到sae上,这个框架使用django实现的,我在这个框架上开发了一个个人blog系统,涉及到图片,文件等单个和批量的上传,这其中就会使用到django自带的ImageField,FileField,包括图片处理(比如缩略图)。其中在网上查了不少资料,最后发现网上的继承基本都是从FieldField继承,其实这并不是我想要的,过于冗余,那么应该怎么做。其实很简单,只需要继承一个storage就可以实现。
上代码(代码中我会有注释,很容易看懂):
创建一个storage.py

##############################################################################storage.py

fromos import environ

debug = not environ.get("APP_NAME", "")                                                               #判断sae环境
from django.utils.translation import ugettext as _
from django.core.files.storage import FileSystemStorage
from django.core.exceptions import ImproperlyConfigured, SuspiciousOperation
from django.conf import settings
import time,os,uuid,random,unicodedata,StringIO
from django.core.files.base import ContentFile
if not debug:
    import sae
    import tempfile
    import sae.storage
    from PIL import Image                     #这里是关键,sae加载Image的方式
else:
    import Image


class SaeAndNotSaeStorage(FileSystemStorage):
    """
    这是一个支持sae和本地django的FileStorage基类
    修改存储文件的路径和基本url
    """
    def __init__(self, location=settings.MEDIA_ROOT, base_url=settings.MEDIA_URL):
        super(SaeAndNotSaeStorage, self).__init__(location, base_url)

    def get_valid_name(self, name):
        """
        这个方法用于验证文件名,我这里的处理方法是去掉中文,我没有找到支持中文名的方法,欢迎补充
        """
        #name = unicodedata.normalize('NFKD', name).encode('ascii', 'ignore')
        #处理中文文件名sae不支持
        if not debug:
            try:
                if 1:
                    #去掉中文
                    name = unicodedata.normalize('NFKD', name).encode('ascii', 'ignore')
                else:
                    for k in name:
                        if self.is_chinese(k):
                            name = "wszw%s"%random.randint(0,100)
            except Exception,e:
                name = "%s.jpg"%type(name)
        #end
        return super(SaeAndNotSaeStorage, self).get_valid_name(name)

    @property
    def maxsize(self):
        return 10*1024*1024#文件2M--sae限制只能传2M,单个文件,据说是10M,其实只有2M

    @property
    def filetypes(self):
        return []

    def makename(self,name):
        #取一个不重复的名字,sae会把重名覆盖
        oname = os.path.basename(name)
        path = os.path.dirname(name)
        #首先判断是否需要重命名---也就是说不想改名字的就加这个前缀
        if oname.find("_mine_")==0:
            oname = oname.replace("_mine_","")
            name = os.path.join(path, oname)
            return name
        #end---首先判断是否需要重命名
        try:
            fname, hk = oname.split(".")
        except Exception,e:
            fname, hk = oname, ''
        if hk:
            rname  = "%s_%s.%s"%(random.randint(0,10000), fname,hk)
        else:
            rname  = "%s_%s"%(random.randint(0,10000), fname)
        name = os.path.join(path, rname)
        #end
        return name

    def _save(self, name, content):
        """
        可以判断上传哪些文件
        """
        hz = name.split(".")[-1]
        #类型判断
        if self.filetypes!='*':
            if hz.lower() not in self.filetypes:
                raise SuspiciousOperation(u"不支持的文件类型,支持%s"%self.filetypes)
        #end
        name = self.makename(name)
        #大小判断
        if content.size > self.maxsize:
            raise SuspiciousOperation(u"文件大小超过限制")
        #end
        #保存
        if not debug:
            s = sae.storage.Client()
            if hasattr(content, '_get_file'):#admin入口
                ob = sae.storage.Object(content._get_file().read())
            else:#view入口(ContentFile)
                ob = sae.storage.Object(content.read())
            url =s.put('media', name, ob)   #注意这里的media,是sae-storage上的domain名
            return name
        else:
            return super(SaeAndNotSaeStorage, self)._save(name, content)
        #end--保存

    def delete(self,name):
        """
       sae的存储空间很宝贵,所有我们在删除图片数据库记录的时候也需要删除图片
        """
        if not debug:
            s = sae.storage.Client()
            try:
                s.delete('media', name)
            except Exception,e:
                pass
        else:
            super(SaeAndNotSaeStorage, self).delete(name)

class ImageStorage(SaeAndNotSaeStorage):
    """
    实现一个ImageField的Storage
    """
    @property
    def maxsize(self):
        return 2*1024*1024#文件2M

    @property
    def filetypes(self):
        return ['jpg','jpeg','png','gif']

class FileStorage(SaeAndNotSaeStorage):
    @property
    def maxsize(self):
        return 10*1024*1024#文件5M

    @property
    def filetypes(self):
        return "*"

    #def makename(self, name):
    #    return name


class ThumbStorage(ImageStorage):
    """
    缩略图-------这个非常关键,处理后的图片在sae上怎么保存,关键就在StringIO
    """ 
    def _save(self, name, content):
        #处理
        image = Image.open(content)
        image = image.convert('RGB')
        image.thumbnail((50, 50), Image.ANTIALIAS)

        output = StringIO.StringIO()
        image.save(output,'JPEG')
        co = ContentFile(output.getvalue())
        output.close()
        #end
        return super(ThumbStorage, self)._save(name, co)


###############################################################################end-storage.py

storage.py的用法如下:
models.py
##############################################################################models.py

from storage import ImageStorage,FileStorage,ThumbStorage
UPLOAD_TO='./upload/%Y%m/%d' #这里会自动创建[年月文件夹/天的文件夹]---图片多的时候分文件夹存放
class UserImages(models.Model):
    user = models.ForeignKey(User, verbose_name=_(u"当前用户"), related_name="userimages")
    picture = models.ImageField(verbose_name=_(u'图片'), max_length=250,
                                     upload_to=UPLOAD_TO,
                                     storage=ImageStorage(),
                                     null=True, blank=True)
    thumb = models.ImageField(verbose_name=_(u'缩略图'), max_length=250,
                                     upload_to=UPLOAD_TO,
                                     storage=ThumbStorage(),
                                     null=True, blank=True)
    afile = models.FileField(verbose_name=_(u"文件[不超过%sM,只能是rar、zip、swf类型]" % MAX_FILE_SIZE), max_length=200,upload_to=UPLOAD_TO,storage=FileStorage())

    def __unicode__(self):
        return u"%s" % self.user

    class Meta:
        verbose_name = _(u'个人图片文件夹')
        verbose_name_plural = verbose_name   

    def delete(self, using=None):
        try:
            self.picture.storage.delete(self.picture.name)
            self.thumb.storage.delete(self.thumb.name)
            self.thumb.storage.delete(self.afile.name)
        except Exception, e:
            pass
        super(UserImages, self).delete(using=using)




##############################################################################end-models.py

最后sae上的配置:
        在sae的storage上创建domain为media的目录
        settings.py的修改:
############################################################# settings.py关键修改[非全部]

from os import environ
debug = not environ.get("APP_NAME", "") 
if debug:
    MEDIA_ROOT = os.path.join(PROJECT_PATH,'media')
else:
    MEDIA_ROOT = "http://yuanxiao-media.sinaapp.com/"  #这里就写你的应用名和domain
if debug:
    MEDIA_URL = '/media/'
else:
    MEDIA_URL = "http://163py.com/media/"  #这里就写你的应用名和domain


############################################################# end--settings.py的修改



最后你可以肆无忌惮的使用django的默认ImageField,FileField和处理图片了。
后记:sae部署还是非常灵活和方便的,希望能帮助到使用django部署的同僚,
            感谢新浪sae给我们这些游散开发者提供一个实现梦想的平台,以前python的支持只有gae,sae是国内第一家,速度快,维护方便。


打个广告,谢谢。
qq: 442360404
我的应用地址:http://yuanxiao.sinaapp.com/

源码见附件。


分享到: 微信 更多