0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-12-01 11:41:20 +01:00

Allow for overriding image upload path

This commit is contained in:
Ben Emery 2015-06-04 11:05:38 +01:00 committed by Matt Westcott
parent 8d91e72407
commit beb2b927d7
7 changed files with 116 additions and 16 deletions

View File

@ -12,6 +12,7 @@ Changelog
* Added `Page` methods `can_exist_under`, `can_create_at`, `can_move_to` for customising page type business rules
* `wagtailadmin.utils.send_mail` now passes extra keyword arguments to Django's `send_mail` function (Matthew Downey)
* `page_unpublish` signal is now fired for each page that was unpublished by a call to `PageQuerySet.unpublish()`
* Add `get_upload_to` method to `AbstractImage`, to allow overriding the default image upload path (Ben Emery)
* Fix: HTTP cache purge now works again on Python 2 (Mitchel Cabuloy)
* Fix: Locked pages can no longer be unpublished (Alex Bridge)
* Fix: Site records now implement `get_by_natural_key`

View File

@ -22,6 +22,7 @@ Minor features
* Added ``Page`` methods ``can_exist_under``, ``can_create_at``, ``can_move_to`` for customising page type business rules
* ``wagtailadmin.utils.send_mail`` now passes extra keyword arguments to Django's ``send_mail`` function (Matthew Downey)
* ``page_unpublish`` signal is now fired for each page that was unpublished by a call to ``PageQuerySet.unpublish()``
* Add `get_upload_to` method to `AbstractImage`, to allow overriding the default image upload path (Ben Emery)
Bug fixes

View File

@ -0,0 +1,42 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
import taggit.managers
import wagtail.wagtailadmin.taggable
import wagtail.wagtailimages.models
class Migration(migrations.Migration):
dependencies = [
('taggit', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('tests', '0018_singletonpage'),
]
operations = [
migrations.CreateModel(
name='CustomImageFilePath',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('title', models.CharField(max_length=255, verbose_name='Title')),
('file', models.ImageField(height_field='height', upload_to=wagtail.wagtailimages.models.get_upload_to, width_field='width', verbose_name='File')),
('width', models.IntegerField(verbose_name='Width', editable=False)),
('height', models.IntegerField(verbose_name='Height', editable=False)),
('created_at', models.DateTimeField(db_index=True, auto_now_add=True, verbose_name='Created at')),
('focal_point_x', models.PositiveIntegerField(null=True, blank=True)),
('focal_point_y', models.PositiveIntegerField(null=True, blank=True)),
('focal_point_width', models.PositiveIntegerField(null=True, blank=True)),
('focal_point_height', models.PositiveIntegerField(null=True, blank=True)),
('tags', taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text=None, verbose_name='Tags')),
('uploaded_by_user', models.ForeignKey(blank=True, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Uploaded by user')),
('file_size', models.PositiveIntegerField(null=True, editable=False)),
],
options={
'abstract': False,
},
bases=(models.Model, wagtail.wagtailadmin.taggable.TagSearchable),
),
]

View File

@ -1,5 +1,8 @@
from __future__ import unicode_literals
import hashlib
import os
from django.db import models
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.utils.encoding import python_2_unicode_compatible
@ -461,7 +464,6 @@ class StreamModel(models.Model):
('image', ImageChooserBlock()),
])
class StreamPage(Page):
body = StreamField([
('text', CharBlock()),
@ -536,3 +538,34 @@ class GenericSnippetPage(Page):
snippet_content_type = models.ForeignKey(ContentType, on_delete=models.SET_NULL, null=True)
snippet_object_id = models.PositiveIntegerField(null=True)
snippet_content_object = GenericForeignKey('snippet_content_type', 'snippet_object_id')
class CustomImageFilePath(AbstractImage):
def get_upload_to(self, filename):
"""Create a path that's file-system friendly.
By hashing the file's contents we guarantee an equal distribution
of files within our root directories. This also gives us a
better chance of uploading images with the same filename, but
different contents - this isn't guaranteed as we're only using
the first three characters of the checksum.
"""
original_filepath = super(CustomImageFilePath, self).get_upload_to(filename)
folder_name, filename = original_filepath.split(os.path.sep)
# Ensure that we consume the entire file, we can't guarantee that
# the stream has not be partially (or entirely) consumed by
# another process
original_position = self.file.tell()
self.file.seek(0)
hash256 = hashlib.sha256()
while True:
data = self.file.read(256)
if not data:
break
hash256.update(data)
checksum = hash256.hexdigest()
self.file.seek(original_position)
return os.path.join(folder_name, checksum[:3], filename)

View File

@ -48,19 +48,9 @@ class ImageQuerySet(SearchableQuerySetMixin, models.QuerySet):
def get_upload_to(instance, filename):
folder_name = 'original_images'
filename = instance.file.field.storage.get_valid_name(filename)
# Dumb proxy to instance method.
return instance.get_upload_to(filename)
# do a unidecode in the filename and then
# replace non-ascii characters in filename with _ , to sidestep issues with filesystem encoding
filename = "".join((i if ord(i) < 128 else '_') for i in unidecode(filename))
# Truncate filename so it fits in the 100 character limit
# https://code.djangoproject.com/ticket/9893
while len(os.path.join(folder_name, filename)) >= 95:
prefix, dot, extension = filename.rpartition('.')
filename = prefix[:-1] + dot + extension
return os.path.join(folder_name, filename)
@python_2_unicode_compatible
@ -106,6 +96,21 @@ class AbstractImage(models.Model, TagSearchable):
return self.file_size
def get_upload_to(self, filename):
folder_name = 'original_images'
filename = self.file.field.storage.get_valid_name(filename)
# do a unidecode in the filename and then
# replace non-ascii characters in filename with _ , to sidestep issues with filesystem encoding
filename = "".join((i if ord(i) < 128 else '_') for i in unidecode(filename))
# Truncate filename so it fits in the 100 character limit
# https://code.djangoproject.com/ticket/9893
while len(os.path.join(folder_name, filename)) >= 95:
prefix, dot, extension = filename.rpartition('.')
filename = prefix[:-1] + dot + extension
return os.path.join(folder_name, filename)
def get_usage(self):
return get_object_usage(self)

View File

@ -7,7 +7,7 @@ from django.core.urlresolvers import reverse
from taggit.forms import TagField, TagWidget
from wagtail.tests.testapp.models import CustomImage
from wagtail.tests.testapp.models import CustomImage, CustomImageFilePath
from wagtail.tests.utils import WagtailTestUtils
from wagtail.wagtailimages.utils import generate_signature, verify_signature
from wagtail.wagtailimages.rect import Rect, Vector
@ -374,3 +374,21 @@ class TestRenditionFilenames(TestCase):
rendition = image.get_rendition('fill-100x100')
self.assertEqual(rendition.file.name, 'images/test_rf3.15ee4958.fill-100x100.png')
class TestDifferentUpload(TestCase):
def test_upload_path(self):
image = CustomImageFilePath.objects.create(
title="Test image",
file=get_test_image_file(),
)
second_image = CustomImageFilePath.objects.create(
title="Test Image",
file=get_test_image_file(colour='black'),
)
# The files should be uploaded based on it's content, not just
# it's filename
self.assertFalse(image.file.url == second_image.file.url)

View File

@ -9,8 +9,8 @@ from wagtail.wagtailimages.models import get_image_model
Image = get_image_model()
def get_test_image_file(filename='test.png'):
def get_test_image_file(filename='test.png', colour='white', size=(640, 480)):
f = BytesIO()
image = PIL.Image.new('RGB', (640, 480), 'white')
image = PIL.Image.new('RGB', size, colour)
image.save(f, 'PNG')
return ImageFile(f, name=filename)