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/
源码见附件。