Ticket #961: new_thumb.diff

File new_thumb.diff, 9.0 KB (added by Nebojša Đorđević - nesh <nesh@…>, 9 years ago)

new thumbnail patch

  • django/core/meta/__init__.py

     
    839839                        setattr(new_class, 'get_%s_width' % f.name, curry(method_get_image_width, f))
    840840                    if not f.height_field:
    841841                        setattr(new_class, 'get_%s_height' % f.name, curry(method_get_image_height, f))
     842                    # thumbnail support
     843                    setattr(new_class, 'get_%s_thumbnail' % f.name, curry(method_get_image_thumbnail, f))
    842844
    843845        # Add the class itself to the new module we've created.
    844846        new_mod.__dict__[name] = new_class
     
    10661068            # delete it from the filesystem.
    10671069            if os.path.exists(file_name) and not opts.get_model_module().get_list(**{'%s__exact' % f.name: getattr(self, f.name)}):
    10681070                os.remove(file_name)
     1071                if isinstance(f, ImageField):
     1072                    # remove thumbnails
     1073                    import fnmatch
     1074                    base, ext = os.path.splitext(os.path.basename(file_name))
     1075                    basedir = os.path.dirname(file_name)
     1076                    for file in fnmatch.filter(os.listdir(basedir), '%s_t*%s' % (base, ext)):
     1077                        os.remove(os.path.join(basedir, file))
     1078
    10691079    # Run any post-delete hooks.
    10701080    if hasattr(self, '_post_delete'):
    10711081        self._post_delete()
     
    13221332        setattr(self, cachename, get_image_dimensions(fname))
    13231333    return getattr(self, cachename)
    13241334
     1335def method_get_image_thumbnail(field, self, width=None, height=None, root=settings.MEDIA_ROOT, url_root=settings.MEDIA_URL):
     1336    from django.parts.media.photos import make_thumbnail
     1337    url = getattr(self, "get_%s_url" % field.name)()
     1338   
     1339    if (width is None) and (height is None):
     1340        return url
     1341
     1342    th = make_thumbnail(url, width, height, root, url_root)
     1343    if th is not None:
     1344        return th
     1345    else:
     1346        return url
     1347
    13251348##############################################
    13261349# HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) #
    13271350##############################################
  • django/parts/media/photos.py

     
    1 import re
     1import re, os
     2from django.conf.settings import MEDIA_ROOT, MEDIA_URL
     3import urlparse
     4# check for PIL
     5try:
     6    import Image
     7    HAS_PIL = True
     8except ImportError:
     9    HAS_PIL = False
    210
    3 def get_thumbnail_url(photo_url, width):
    4     bits = photo_url.split('/')
    5     bits[-1] = re.sub(r'(?i)\.(gif|jpg)$', '_t%s.\\1' % width, bits[-1])
    6     return '/'.join(bits)
     11def _get_thumbnail_path(path, width=None, height=None):
     12    """ create thumbnail path from path and required width and/or height.
     13   
     14        thumbnail file name is constructed like this:
     15            <basename>_t_[w<width>][_h<height>].<extension>
     16    """
     17   
     18    # one of width/height is required
     19    assert (width is not None) or (height is not None)
     20
     21    basedir = os.path.dirname(path) + '/'
     22    base, ext = os.path.splitext(os.path.basename(path))
     23   
     24    # make thumbnail filename
     25    th_name = base + '_t'
     26    if (width is not None) and (height is not None):
     27        th_name += '_w%d_h%d' % (width, height)
     28    elif width is not None:
     29        th_name += '_w%d' % width
     30    elif height is not None:
     31        th_name += '_h%d' % height
     32    th_name += ext
     33   
     34    return urlparse.urljoin(basedir, th_name)
     35#
     36
     37def get_path_from_url(url, root=MEDIA_ROOT, url_root=MEDIA_URL):
     38    """ make filesystem path from url """
     39
     40    if url.startswith(url_root):
     41        url = url[len(url_root):] # strip media root url
     42
     43    return os.path.normpath(os.path.join(root, url))
     44#
     45
     46def get_url_from_path(path, root=MEDIA_ROOT, url_root=MEDIA_URL):
     47    """ make url from filesystem path """
     48
     49    if path.startswith(root):
     50        path = path[len(root):] # strip media root
     51   
     52    return urlparse.urljoin(root, path.replace('\\', '/'))
     53#
     54
     55def has_thumbnail(photo_url, width=None, height=None, root=MEDIA_ROOT, url_root=MEDIA_URL):
     56    # one of width/height is required
     57    assert (width is not None) or (height is not None)
     58
     59    return os.path.isfile(get_path_from_url(_get_thumbnail_path(photo_url, width, height), root, url_root))
     60#
     61
     62def make_thumbnail(photo_url, width=None, height=None, root=MEDIA_ROOT, url_root=MEDIA_URL):
     63    """ create thumbnail """
     64   
     65    # one of width/height is required
     66    assert (width is not None) or (height is not None)
     67   
     68    if not HAS_PIL: return None # no PIL - no thumbnail
     69
     70    th_url = _get_thumbnail_path(photo_url, width, height)
     71    th_path = get_path_from_url(th_url, root, url_root)
     72    photo_path = get_path_from_url(photo_url, root, url_root)
     73   
     74    if has_thumbnail(photo_url, width, height, root, url_root):
     75        # thumbnail already exists
     76        if not (os.path.getmtime(photo_path) > os.path.getmtime(th_path)):
     77            # if photo mtime is newer than thumbnail recreate thumbnail
     78            return th_url
     79   
     80    # make thumbnail
     81   
     82    # get original image size
     83    orig_w, orig_h = get_image_size(photo_url, root, url_root)
     84   
     85    if (orig_w == width) and (orig_h == height):
     86        # same dimensions
     87        return None
     88
     89    img = Image.open(photo_path).copy()
     90    # make proper size
     91    if (width is not None) and (height is not None):
     92        size = (width, height)
     93    elif width is not None:
     94        size = (width, orig_h)
     95    elif height is not None:
     96        size = (orig_w, height)
     97
     98    img.thumbnail(size, Image.ANTIALIAS)
     99    img.save(th_path)
     100
     101    return th_url
     102#
     103
     104def get_thumbnail_url(photo_url, width=None, height=None, root=MEDIA_ROOT, url_root=MEDIA_URL):
     105    """ return thumbnail URL for requested photo_url and required width and/or height
     106   
     107        if thumbnail file do not exists returns original URL
     108    """
     109
     110    # one of width/height is required
     111    assert (width is not None) or (height is not None)
     112   
     113    if has_thumbnail(photo_url, width, height, root, url_root):
     114        return _get_thumbnail_path(photo_url, width, height)
     115    else:
     116        return photo_url
     117
     118def _no_pil_image_size(fname):
     119    """
     120        Determine the image type of FNAME and return its size.
     121        ripped from draco
     122       
     123        returns tuple (width, height) or None
     124    """
     125   
     126    try:
     127        filehandle = file(fname, 'rb')
     128    except IOError:
     129        return None
     130
     131    head = filehandle.read(24)
     132    if len(head) != 24:
     133        return
     134    if head[:4] == '\x89PNG':
     135        # PNG
     136        check = struct.unpack('>i', head[4:8])[0]
     137        if check != 0x0d0a1a0a:
     138            return
     139        width, height = struct.unpack('>ii', head[16:24])
     140    elif head[:6] in ('GIF87a', 'GIF89a'):
     141        # GIF
     142        width, height = struct.unpack('<HH', head[6:10])
     143    elif head[:4] == '\xff\xd8\xff\xe0' and head[6:10] == 'JFIF':
     144        # JPEG
     145        try:
     146            filehandle.seek(0)  # Read 0xff next
     147            size = 2
     148            filetype = 0
     149            while not 0xc0 <= filetype <= 0xcf:
     150                filehandle.seek(size, 1)
     151                byte = filehandle.read(1)
     152                while ord(byte) == 0xff:
     153                    byte = filehandle.read(1)
     154                filetype = ord(byte)
     155                size = struct.unpack('>H', filehandle.read(2))[0] - 2
     156            # We are at a SOFn block
     157            filehandle.seek(1, 1)  # Skip `precision' byte.
     158            height, width = struct.unpack('>HH', filehandle.read(4))
     159        except:
     160            raise
     161            return
     162    else:
     163        return
     164    return width, height
     165#
     166
     167def get_image_size(photo_url, root=MEDIA_ROOT, url_root=MEDIA_URL):
     168    """ returns image size, use PIL if present or _no_pil_image_size if no PIL is found.
     169   
     170        TODO: add image size caching.
     171    """
     172   
     173    path = get_path_from_url(photo_url, root, url_root)
     174   
     175    if HAS_PIL:
     176        size = Image.open(path).size
     177    else:
     178        size = _no_pil_image_size(path)
     179
     180    return size
     181#
  • django/contrib/admin/templatetags/admin_list.py

     
    151151            # ImageFields are special: Use a thumbnail.
    152152            elif isinstance(f, meta.ImageField):
    153153                from django.parts.media.photos import get_thumbnail_url
    154                 result_repr = '<img src="%s" alt="%s" title="%s" />' % (get_thumbnail_url(getattr(result, 'get_%s_url' % f.name)(), '120'), field_val, field_val)
     154                result_repr = '<img src="%s" alt="%s" title="%s" />' % (getattr(result, 'get_%s_thumbnail' % f.name)(width=120), field_val, field_val)
    155155            # FloatFields are special: Zero-pad the decimals.
    156156            elif isinstance(f, meta.FloatField):
    157157                if field_val is not None:
Back to Top