这两天做了一个定制django-imagekit生成图片名称的任务,本来想简单写写《如何定制Django-imagekit的ImageSpecField名称规则》,但是想到这个东西这么吊,还是多废话几句吧。
先说说场景,无论是做哪种业务,但凡是涉及到图片的,一定会有裁图的需求。但是裁图的需求又分好几种,主要的两种是:一种是被动裁图,可以裁出各种尺寸或限定的几种尺寸;另外一种是主动裁图,裁出固定的几种尺寸。
根据以往的项目实践中,我们往往是采用前一种做法,通过nignx的一个裁图模块image_filter,根据URL的参数来裁图,比方说,有原图:/images/the5fire.png, 通过请求 /images/the5fire.png/90x90 就可以得到一个90x90的方图,这是一例,被动裁图——接受到用户请求之后才处理图片。
另外一种是主动裁图,通过nginx的模块也能做,做法就是程序先自动去请求一遍。这种方式的另外一种做法就是自己在程序中裁图,对于读多写少的应用来说还挺合适。在Python程序中怎么处理裁图的需求呢?有个Pillow的库,也是很强大。如果在Django中处理,那就要考虑是不是有人已经在Pillow上做好了更符合Django流程的封装了,Django-imagekit就是这么个东西!
按照我们的需求,提供几种比例相同尺寸不同的图片,用Django-imagekit完全满足需求,当然最重要的是,很易用。
举个例子,定义个Model:
from django.db import models
from imagekit.models import ImageSpecField
from imagekit.processors import ResizeToFill
class Author(models.Model):
avator = models.ImageField(
upload_to="horizontal", blank=True,
verbose_name="原图")
avator_90x90 = ImageSpecField(
source="avator", processors=[ResizeToFill(90, 90)],
format='JPEG', options={'quality': 95})
avator_30x30 = ImageSpecField(
source="avator", processors=[ResizeToFill(30, 30)],
format='JPEG', options={'quality': 95})
# ImageSpecField不会生成数据库中的表
这样定义完之后,在xadmin后台我只需要上传一个原图,在访问的时候会自动生成90x90和30x30的图,访问的时候只需要author.avator_90x90.url即可。当然在模板上Django-imagekit也提供的一些tags,可以直接在模板中进行图片处理。
之前说的是主动裁图的方式,imagekit提供了几种生成图的策略:内容获取,路径访问,原图保存。主动裁图就是使用原图保存时进行生成的策略。另外值得一提的是,它还支持异步的方式来生成图片,需要用到Celery。
一开始有说到自定义生成图的路径的问题。默认情况下,imagekit使用 imagekit.cachefiles.namers.source_name_as_path
来生成图片的路径。大体逻辑是,根据你设置的参数:processors,format,options进行pickle,把数据写到一个内存文件中,然后根据文件内容做一个md5,作为生成这个尺寸图片的名称。这么说比较抽象,还是举个例子:原图:/image/the5fire.png 生成图 /CACHE/images/the5fire/123dadwdniijonsd.jpg,这不是实际的生成结果,只是示例,最后的 123xxxsd
可以当做那个md5的值,当然前面的 /CACHE/images/
这样的路径也可以定制。
按照这样的逻辑,使用imagekit的话就不需要再定义一个数据库字段来存放,只需要根据原图路径加上这个规则,就能取到对应的图片。
如果所有项目都用imagekit还好,默认的生成规则就行,但是如果其他项目需要用到你这个数据库中的内容,单独提供接口,并且不是使用Django甚至也不是Python开发,那这种逻辑就很复杂了。这种根据参数进行pickle,然后计算md5值的逻辑在其他项目中根本无法使用,因为其他项目没有你的这个Model定义。
基于此情景,需要自定义路径,也就是imagekit中的 namer
。自定义的逻辑很简单,只需要在Django中设置IMAGEKIT_SPEC_CACHEFILE_NAMER使用你自定义的方法,自定义的逻辑可以是这样, 简单copy并修改默认的source_name_as_path的代码即可:
def spec_namer(generator):
try:
width = generator.processors[0].width
height = generator.processors[0].height
quality = generator.options.get('quality', 'default')
filename = '%s_%s_s' % (width, height, quality)
except (IndexError, AttributeError):
return source_name_as_path(generator)
source_filename = getattr(generator.source, 'name', None)
if source_filename is None or os.path.isabs(source_filename):
# Generally, we put the file right in the cache file directory.
dir = settings.IMAGEKIT_CACHEFILE_DIR
else:
# For source files with relative names (like Django media files),
# use the source's name to create the new filename.
dir = os.path.join(settings.IMAGEKIT_CACHEFILE_DIR,
os.path.splitext(source_filename)[0])
ext = suggest_extension(source_filename or '', generator.format)
return os.path.normpath(os.path.join(dir, '%s%s' % (filename, ext)))
这样就可以了,生成规则就是: CACHE/images/ + 原图路径 + 宽_高_质量 + .jpg 。 注意,设置format为JPEG,后缀就是jpg。
imagekit就简单介绍到这里,功能还有很多,比如水印,去边框等等。如果有这类的使用场景,不妨试试:
http://django-imagekit.readthedocs.org/en/latest/index.html
- from the5fire.com微信公众号:Python程序员杂谈