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