Browse Source

Feature: Built django-images into Pinry

pull/128/head
winkidney 6 years ago
parent
commit
a59f3cf9eb
16 changed files with 609 additions and 6 deletions
  1. +1
    -1
      Pipfile
  2. +16
    -5
      Pipfile.lock
  3. +0
    -0
      django_images/__init__.py
  4. +38
    -0
      django_images/migrations/0001_initial.py
  5. +0
    -0
      django_images/migrations/__init__.py
  6. +132
    -0
      django_images/models.py
  7. +5
    -0
      django_images/settings.py
  8. +65
    -0
      django_images/south_migrations/0001_initial.py
  9. +45
    -0
      django_images/south_migrations/0002_change_thumbnail_unique_constraint.py
  10. +0
    -0
      django_images/south_migrations/__init__.py
  11. +0
    -0
      django_images/templatetags/__init__.py
  12. +8
    -0
      django_images/templatetags/images.py
  13. +206
    -0
      django_images/tests.py
  14. +6
    -0
      django_images/urls.py
  15. +73
    -0
      django_images/utils.py
  16. +14
    -0
      django_images/views.py

+ 1
- 1
Pipfile View File

@@ -5,13 +5,13 @@ verify_ssl = true

[dev-packages]
"flake8" = "*"
qrcode = "*"

[packages]
django = ">=1.11,<1.12"
pillow = "*"
requests = "*"
django-taggit = "*"
django-images = {git = "https://github.com/winkidney/django-images.git"}
django-braces = "*"
django-compressor = "*"
django-tastypie = ">=0.13.0,<0.14"


+ 16
- 5
Pipfile.lock View File

@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "8fd24fa32e2a3375d13a2df0faa5dee6e50845bff8dd6c0bf10d6ba970a8e7d4"
"sha256": "c632e45ac592ec9c159c042db8c52e221ae133ade94cd11fcfb124a5fd5b9dd0"
},
"pipfile-spec": 6,
"requires": {},
@@ -59,10 +59,6 @@
"index": "pypi",
"version": "==2.2"
},
"django-images": {
"git": "https://github.com/winkidney/django-images.git",
"ref": "5c22e931145d2f924c06fcf5dcf425068cfa0fe9"
},
"django-taggit": {
"hashes": [
"sha256:a21cbe7e0879f1364eef1c88a2eda89d593bf000ebf51c3f00423c6927075dce",
@@ -274,6 +270,21 @@
"sha256:8d616a382f243dbf19b54743f280b80198be0bca3a5396f1d2e1fca6223e8805"
],
"version": "==1.6.0"
},
"qrcode": {
"hashes": [
"sha256:037b0db4c93f44586e37f84c3da3f763874fcac85b2974a69a98e399ac78e1bf",
"sha256:de4ffc15065e6ff20a551ad32b6b41264f3c75275675406ddfa8e3530d154be3"
],
"index": "pypi",
"version": "==6.0"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
}
}
}

+ 0
- 0
django_images/__init__.py View File


+ 38
- 0
django_images/migrations/0001_initial.py View File

@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models
import django_images.models


class Migration(migrations.Migration):

dependencies = [
]

operations = [
migrations.CreateModel(
name='Image',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('image', models.ImageField(height_field=b'height', width_field=b'width', max_length=255, upload_to=django_images.models.hashed_upload_to)),
('height', models.PositiveIntegerField(default=0, editable=False)),
('width', models.PositiveIntegerField(default=0, editable=False)),
],
),
migrations.CreateModel(
name='Thumbnail',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('image', models.ImageField(height_field=b'height', width_field=b'width', max_length=255, upload_to=django_images.models.hashed_upload_to)),
('size', models.CharField(max_length=100)),
('height', models.PositiveIntegerField(default=0, editable=False)),
('width', models.PositiveIntegerField(default=0, editable=False)),
('original', models.ForeignKey(to='django_images.Image')),
],
),
migrations.AlterUniqueTogether(
name='thumbnail',
unique_together=set([('original', 'size')]),
),
]

+ 0
- 0
django_images/migrations/__init__.py View File


+ 132
- 0
django_images/models.py View File

@@ -0,0 +1,132 @@
import hashlib
import os.path
from io import BytesIO

from django.db import models
from django.core.files.uploadedfile import InMemoryUploadedFile
from django.core.urlresolvers import reverse
from django.dispatch import receiver
import PIL

try:
from importlib import import_module
except ImportError:
from django.utils.importlib import import_module

from . import utils
from .settings import IMAGE_SIZES, IMAGE_PATH, IMAGE_AUTO_DELETE


def hashed_upload_to(instance, filename, **kwargs):
image_type = 'original' if isinstance(instance, Image) else 'thumbnail'
prefix = 'image/%s/by-md5/' % (image_type,)
hasher = hashlib.md5()
for chunk in instance.image.chunks():
hasher.update(chunk)
hash_ = hasher.hexdigest()
base, ext = os.path.splitext(filename)
return '%(prefix)s%(first)s/%(second)s/%(hash)s/%(base)s%(ext)s' % {
'prefix': prefix,
'first': hash_[0],
'second': hash_[1],
'hash': hash_,
'base': base,
'ext': ext,
}


if IMAGE_PATH is None:
upload_to = hashed_upload_to
else:
if callable(IMAGE_PATH):
upload_to = IMAGE_PATH
else:
parts = IMAGE_PATH.split('.')
module_name = '.'.join(parts[:-1])
module = import_module(module_name)
upload_to = getattr(module, parts[-1])


class Image(models.Model):
image = models.ImageField(upload_to=upload_to,
height_field='height', width_field='width',
max_length=255)
height = models.PositiveIntegerField(default=0, editable=False)
width = models.PositiveIntegerField(default=0, editable=False)

def get_by_size(self, size):
return self.thumbnail_set.get(size=size)

def get_absolute_url(self, size=None):
if not size:
return self.image.url
try:
return self.get_by_size(size).image.url
except Thumbnail.DoesNotExist:
return reverse('image-thumbnail', args=(self.id, size))


class ThumbnailManager(models.Manager):
def get_or_create_at_size(self, image_id, size):
image = Image.objects.get(id=image_id)
if size not in IMAGE_SIZES:
raise ValueError("Received unknown size: %s" % size)
try:
thumbnail = image.get_by_size(size)
except Thumbnail.DoesNotExist:
img = utils.scale_and_crop(image.image, **IMAGE_SIZES[size])
# save to memory
buf = BytesIO()
try:
img.save(buf, img.format, **img.info)
except IOError:
if img.info.get('progression'):
orig_MAXBLOCK = PIL.ImageFile.MAXBLOCK
temp_MAXBLOCK = 1048576
if orig_MAXBLOCK >= temp_MAXBLOCK:
raise
PIL.ImageFile.MAXBLOCK = temp_MAXBLOCK
try:
img.save(buf, img.format, **img.info)
finally:
PIL.ImageFile.MAXBLOCK = orig_MAXBLOCK
else:
raise
# and save to storage
original_dir, original_file = os.path.split(image.image.name)
thumb_file = InMemoryUploadedFile(buf, "image", original_file,
None, buf.tell(), None)
thumbnail, created = image.thumbnail_set.get_or_create(
size=size, defaults={'image': thumb_file})
return thumbnail


class Thumbnail(models.Model):
original = models.ForeignKey(Image)
image = models.ImageField(upload_to=upload_to,
height_field='height', width_field='width',
max_length=255)
size = models.CharField(max_length=100)
height = models.PositiveIntegerField(default=0, editable=False)
width = models.PositiveIntegerField(default=0, editable=False)

objects = ThumbnailManager()

class Meta:
unique_together = ('original', 'size')

def get_absolute_url(self):
return self.image.url


@receiver(models.signals.post_save)
def original_changed(sender, instance, created, **kwargs):
if isinstance(instance, Image):
instance.thumbnail_set.all().delete()


@receiver(models.signals.post_delete)
def delete_image_files(sender, instance, **kwargs):
if isinstance(instance, (Image, Thumbnail)) and IMAGE_AUTO_DELETE:
if instance.image.storage.exists(instance.image.name):
instance.image.delete(save=False)

+ 5
- 0
django_images/settings.py View File

@@ -0,0 +1,5 @@
from django.conf import settings

IMAGE_PATH = getattr(settings, 'IMAGE_PATH', None)
IMAGE_SIZES = getattr(settings, 'IMAGE_SIZES', {})
IMAGE_AUTO_DELETE = getattr(settings, 'IMAGE_AUTO_DELETE', True)

+ 65
- 0
django_images/south_migrations/0001_initial.py View File

@@ -0,0 +1,65 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models


class Migration(SchemaMigration):

def forwards(self, orm):
# Adding model 'Image'
db.create_table(u'django_images_image', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('image', self.gf('django.db.models.fields.files.ImageField')(max_length=255)),
('height', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
('width', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
))
db.send_create_signal(u'django_images', ['Image'])

# Adding model 'Thumbnail'
db.create_table(u'django_images_thumbnail', (
(u'id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
('original', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['django_images.Image'])),
('image', self.gf('django.db.models.fields.files.ImageField')(max_length=255)),
('size', self.gf('django.db.models.fields.CharField')(max_length=100)),
('height', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
('width', self.gf('django.db.models.fields.PositiveIntegerField')(default=0)),
))
db.send_create_signal(u'django_images', ['Thumbnail'])

# Adding unique constraint on 'Thumbnail', fields ['image', 'size']
db.create_unique(u'django_images_thumbnail', ['image', 'size'])


def backwards(self, orm):
# Removing unique constraint on 'Thumbnail', fields ['image', 'size']
db.delete_unique(u'django_images_thumbnail', ['image', 'size'])

# Deleting model 'Image'
db.delete_table(u'django_images_image')

# Deleting model 'Thumbnail'
db.delete_table(u'django_images_thumbnail')


models = {
u'django_images.image': {
'Meta': {'object_name': 'Image'},
'height': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255'}),
'width': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
u'django_images.thumbnail': {
'Meta': {'unique_together': "(('image', 'size'),)", 'object_name': 'Thumbnail'},
'height': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255'}),
'original': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['django_images.Image']"}),
'size': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'width': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
}
}

complete_apps = ['django_images']

+ 45
- 0
django_images/south_migrations/0002_change_thumbnail_unique_constraint.py View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*-
import datetime
from south.db import db
from south.v2 import SchemaMigration
from django.db import models


class Migration(SchemaMigration):

def forwards(self, orm):
# Removing unique constraint on 'Thumbnail', fields ['image', 'size']
db.delete_unique(u'django_images_thumbnail', ['image', 'size'])

# Adding unique constraint on 'Thumbnail', fields ['original', 'size']
db.create_unique(u'django_images_thumbnail', ['original_id', 'size'])


def backwards(self, orm):
# Removing unique constraint on 'Thumbnail', fields ['original', 'size']
db.delete_unique(u'django_images_thumbnail', ['original_id', 'size'])

# Adding unique constraint on 'Thumbnail', fields ['image', 'size']
db.create_unique(u'django_images_thumbnail', ['image', 'size'])


models = {
u'django_images.image': {
'Meta': {'object_name': 'Image'},
'height': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255'}),
'width': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
},
u'django_images.thumbnail': {
'Meta': {'unique_together': "(('original', 'size'),)", 'object_name': 'Thumbnail'},
'height': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'}),
u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'image': ('django.db.models.fields.files.ImageField', [], {'max_length': '255'}),
'original': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['django_images.Image']"}),
'size': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
'width': ('django.db.models.fields.PositiveIntegerField', [], {'default': '0'})
}
}

complete_apps = ['django_images']

+ 0
- 0
django_images/south_migrations/__init__.py View File


+ 0
- 0
django_images/templatetags/__init__.py View File


+ 8
- 0
django_images/templatetags/images.py View File

@@ -0,0 +1,8 @@
from django import template

register = template.Library()


@register.filter
def at_size(image, size):
return image.get_absolute_url(size=size)

+ 206
- 0
django_images/tests.py View File

@@ -0,0 +1,206 @@
import mock
import qrcode
from django.test import TestCase
from django.core.files.images import ImageFile
from django.conf import settings
from django.utils.six import BytesIO
from django.core.urlresolvers import reverse
from django_images.models import Image, Thumbnail
from django_images.templatetags.images import at_size
from django_images.utils import scale_and_crop


class ImageModelTest(TestCase):
def setUp(self):
image_obj = BytesIO()
qrcode_obj = qrcode.make('https://mirumee.com/')
qrcode_obj.save(image_obj)
self.image = Image.objects.create(width=370, height=370,
image=ImageFile(image_obj, '01.png'))

def test_get_by_size(self):
size = list(settings.IMAGE_SIZES.keys())[0]
thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)
self.image.get_by_size(size)

def test_get_absolute_url(self):
url = self.image.get_absolute_url()
self.assertEqual(url, self.image.image.url)
# For thumbnail
size = list(settings.IMAGE_SIZES.keys())[0]
thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)
url = self.image.get_absolute_url(size)
self.assertEqual(url, thumb.image.url)
# Fallback on creation url
size = list(settings.IMAGE_SIZES.keys())[1]
url = self.image.get_absolute_url(size)
fallback_url = reverse('image-thumbnail', args=(self.image.id, size))
self.assertEqual(url, fallback_url)


class ThumbnailManagerModelTest(TestCase):
def setUp(self):
image_obj = BytesIO()
qrcode_obj = qrcode.make('https://mirumee.com/')
qrcode_obj.save(image_obj)
self.image = Image.objects.create(width=370, height=370,
image=ImageFile(image_obj, '01.png'))
self.size = list(settings.IMAGE_SIZES.keys())[0]

def test_unknown_size(self):
self.assertRaises(ValueError, Thumbnail.objects.get_or_create_at_size,
self.image.id, 'foo')

# TODO: Test the image object and data
def test_create(self):
thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, self.size)
self.assertEqual(self.image.thumbnail_set.count(), 1)

def test_get(self):
thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, self.size)
thumb2 = Thumbnail.objects.get_or_create_at_size(self.image.id, self.size)
self.assertEqual(thumb.id, thumb2.id)


class ThumbnailModelTest(TestCase):
def setUp(self):
image_obj = BytesIO()
qrcode_obj = qrcode.make('https://mirumee.com/')
qrcode_obj.save(image_obj)
self.image = Image.objects.create(width=370, height=370,
image=ImageFile(image_obj, '01.png'))
size = list(settings.IMAGE_SIZES.keys())[0]
self.thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)

def test_get_absolute_url(self):
url = self.thumb.get_absolute_url()
self.assertEqual(url, self.thumb.image.url)


class PostSaveSignalOriginalChangedTestCase(TestCase):
def setUp(self):
image_obj = BytesIO()
qrcode_obj = qrcode.make('https://mirumee.com/')
qrcode_obj.save(image_obj)
self.image = Image.objects.create(width=370, height=370,
image=ImageFile(image_obj, '01.png'))
size = list(settings.IMAGE_SIZES.keys())[0]
self.thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)

def test_post_save_signal_original_changed(self):
size = list(settings.IMAGE_SIZES.keys())[0]
thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)
self.image.delete()
self.assertFalse(Thumbnail.objects.exists())


class PostDeleteSignalDeleteImageFileTest(TestCase):
def setUp(self):
image_obj = BytesIO()
qrcode_obj = qrcode.make('https://mirumee.com/')
qrcode_obj.save(image_obj)
self.image = Image.objects.create(width=370, height=370,
image=ImageFile(image_obj, '01.png'))
size = list(settings.IMAGE_SIZES.keys())[0]
self.thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)

@mock.patch('django_images.models.IMAGE_AUTO_DELETE', True)
def test_post_delete_signal_delete_image_files_enabled(self):
storage = self.image.image.storage
image_name = self.image.image.name
thumb_name = self.thumb.image.name
self.image.delete()
self.assertFalse(storage.exists(image_name))
self.assertFalse(storage.exists(thumb_name))

@mock.patch('django_images.models.IMAGE_AUTO_DELETE', False)
def test_post_delete_signal_delete_image_files_disabled(self):
storage = self.image.image.storage
image_name = self.image.image.name
thumb_name = self.thumb.image.name
# Delete thumb
self.thumb.delete()
self.assertTrue(storage.exists(image_name))
self.assertTrue(storage.exists(thumb_name))
# Delete image
self.image.delete()
self.assertTrue(storage.exists(image_name))
self.assertTrue(storage.exists(thumb_name))


class AtSizeTemplateTagTest(TestCase):
def setUp(self):
image_obj = BytesIO()
qrcode_obj = qrcode.make('https://mirumee.com/')
qrcode_obj.save(image_obj)
self.image = Image.objects.create(width=370, height=370,
image=ImageFile(image_obj, '01.png'))
size = list(settings.IMAGE_SIZES.keys())[0]
self.thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, size)

def test_at_size(self):
size = list(settings.IMAGE_SIZES.keys())[0]
url = at_size(self.image, size)
self.assertEqual(url, self.thumb.image.url)


class ThumbnailViewTest(TestCase):
def setUp(self):
image_obj = BytesIO()
qrcode_obj = qrcode.make('https://mirumee.com/')
qrcode_obj.save(image_obj)
self.image = Image.objects.create(width=370, height=370,
image=ImageFile(image_obj, '01.png'))
self.size = list(settings.IMAGE_SIZES.keys())[0]
self.thumb = Thumbnail.objects.get_or_create_at_size(self.image.id, self.size)

def test_redirect(self):
url = reverse('image-thumbnail', args=[self.image.id, self.size])
response = self.client.get(url)
self.assertRedirects(response, self.thumb.image.url)

def test_not_found(self):
url = reverse('image-thumbnail', args=['42', self.size])
response = self.client.get(url)
self.assertEqual(response.status_code, 404)

def test_size_not_found(self):
url = reverse('image-thumbnail', args=[self.image.id, '42'])
response = self.client.get(url)
self.assertEqual(response.status_code, 404)


class UtilsScaleAndDropTest(TestCase):
def setUp(self):
image_obj = BytesIO()
qrcode_obj = qrcode.make('https://mirumee.com/')
qrcode_obj.save(image_obj)
self.imagefile = ImageFile(image_obj, '01.png')

def test_change_size(self):
new_size = (10, 10)
image = scale_and_crop(self.imagefile, new_size)
self.assertEqual(new_size, image.im.size)

def test_crop(self):
new_size = (10, 10)
image = scale_and_crop(self.imagefile, new_size, crop=True)
self.assertEqual(new_size, image.im.size)

def test_disabled_upscale(self):
image = scale_and_crop(self.imagefile, (740, 740), upscale=False)
self.assertLess(image.im.size[0], 371)
self.assertLess(image.im.size[1], 371)

def test_enaabled_upscale(self):
image = scale_and_crop(self.imagefile, (740, 740), upscale=True)
self.assertGreater(image.im.size[0], 371)
self.assertGreater(image.im.size[1], 371)

def test_not_change_quality(self):
image = scale_and_crop(self.imagefile, (10, 10), quality=None)
self.assertEqual(image.info.get('quality'), None)

def test_change_quality(self):
image = scale_and_crop(self.imagefile, (10, 10), quality=50)
self.assertEqual(image.info.get('quality'), 50)

+ 6
- 0
django_images/urls.py View File

@@ -0,0 +1,6 @@
from django.conf.urls import include, url
from . import views

urlpatterns = [
url(r'^thumbnail/(?P<image_id>\d+)/(?P<size>[^/]+)/$', views.thumbnail, name='image-thumbnail'),
]

+ 73
- 0
django_images/utils.py View File

@@ -0,0 +1,73 @@
from PIL import Image


# this neat function is based on easy-thumbnails
def scale_and_crop(image, size, crop=False, upscale=False, quality=None):
"""
Resize, crop and/or change quality of an image.

:param image: Source image file
:param type: :class:`django.core.files.images.ImageFile`

:param size: Size as width & height, zero as either means unrestricted
:type size: tuple of two int

:param crop: Truncate image or not
:type crop: bool

:param upscale: Enable scale up
:type upscale: bool

:param quality: Value between 1 to 95, or None for keep the same
:type quality: int or NoneType

:return: Handled image
:rtype: class:`PIL.Image`
"""
# Open image and store format/metadata.
image.open()
im = Image.open(image)
im_format, im_info = im.format, im.info
if quality:
im_info['quality'] = quality

# Force PIL to load image data.
im.load()

source_x, source_y = [float(v) for v in im.size]
target_x, target_y = [float(v) for v in size]

if crop or not target_x or not target_y:
scale = max(target_x / source_x, target_y / source_y)
else:
scale = min(target_x / source_x, target_y / source_y)

# Handle one-dimensional targets.
if not target_x:
target_x = source_x * scale
elif not target_y:
target_y = source_y * scale

if scale < 1.0 or (scale > 1.0 and upscale):
im = im.resize((int(source_x * scale), int(source_y * scale)),
resample=Image.ANTIALIAS)

if crop:
# Use integer values now.
source_x, source_y = im.size
# Difference between new image size and requested size.
diff_x = int(source_x - min(source_x, target_x))
diff_y = int(source_y - min(source_y, target_y))
if diff_x or diff_y:
# Center cropping (default).
halfdiff_x, halfdiff_y = diff_x // 2, diff_y // 2
box = [halfdiff_x, halfdiff_y,
min(source_x, int(target_x) + halfdiff_x),
min(source_y, int(target_y) + halfdiff_y)]
# Finally, crop the image!
im = im.crop(box)

# Close image and replace format/metadata, as PIL blows this away.
im.format, im.info = im_format, im_info
image.close()
return im

+ 14
- 0
django_images/views.py View File

@@ -0,0 +1,14 @@
from django.http import HttpResponseNotFound
from django.shortcuts import get_object_or_404, redirect

from . import models
from .settings import IMAGE_SIZES


def thumbnail(request, image_id, size):
image = get_object_or_404(models.Image, id=image_id)
if size not in IMAGE_SIZES:
return HttpResponseNotFound()

return redirect(models.Thumbnail.objects.get_or_create_at_size(image.id,
size))

Loading…
Cancel
Save