How to resize source with sorl-thumbnail? - django

I was searching the web for my question and couldn't find a clear answeror any example.
Basically, I want to use sorl and want to resize the source image during the Model save time to resize it down to 640x480 size, so that I don't end-up storing user's original 2.5 MB files on the disk. I will then use templatetags to create regular thumbnails out of my source as documented in sorl.
I came across couple of sources refer to use ThumbnailField model field that is supposed to be available in sorl.thumbnail.fields. See the link here. However, in my up-to-date sorl copy from the trunk I don't see any ThumbnailField or ImageWithThumbnailsField. My attempt to import it in the model fails accordingly. I see these references are old though and wondering whether I can achieve the same with up-to-date sorl.
On the other hand sorl documentation indicates only ImageField from sorl.thumbnail (see here) that does not have any size argument to control the source resizing.
BTW, I see this functionality is available with easy_thumbnail that takes an input parameter source_resize.
Any help will be appreciated!
SUMMARY
I accepted the below answer, however I feel natural sorl support for this use case can be very useful - i.e. adding resize_source param to sorl's ImageField to allow resizing the source image. Below are two factors why this can be useful in the field:
Not to store user's huge original images if your app has no need for it. Saving disk space.
Not to spend extra CPU for resizing thumbnails from that huge source images if you don't have specific extreme high quality reasons. To avoid this one may write nested tags in templates to thumbnail from smaller size images but it can become annoying very soon.

I found a flaw in the code above, got “str has no method chunck()”, if somebody want to use it. Here is my fix:
from sorl.thumbnail import get_thumbnail
from django.core.files.base import ContentFile
class Foo(models.Model):
image = models.ImageField(upload_to...)
def save(self, *args, **kwargs):
if not self.id:
super(Foo, self).save(*args, **kwargs)
resized = get_thumbnail(self.image, "100x100" ...)
self.image.save(resized.name, ContentFile(resized.read()), True)
super(Foo, self).save(*args, **kwargs)

Sorl's ImageField that you mention, is just a normal Django ImageField with the added benefit of managing the deletion of cached thumbnails. There's no resizing done on the initial upload - that's something you have to implement yourself manually via the view you are using to upload. The docs show how to go about this. You can use sorl in that view to do the actual resize operation itself, using the low level API examlpes
EDIT
A quicker alternative is to just resize the image when the model is being saved using sorl. You can do something like the following (completely untested though!)
from sorl.thumbnail import get_thumbnail
class Foo(models.Model):
image = models.ImageField(upload_to...)
def save(self, *args, **kwargs):
if not self.id:
# Have to save the image (and imagefield) first
super(Foo, self).save(*args, **kwargs)
# obj is being created for the first time - resize
resized = get_thumbnail(self.image, "100x100" ...)
# Manually reassign the resized image to the image field
self.image.save(resized.name, resized.read(), True)
super(Foo, self).save(*args, **kwargs)
this will mean that you will have 2 versions of the same image on disk - one where the django image field decides to save it (upload_to path) and one where sorl thumbnail has saved it's resized thumbnail. This, along with the fact the image is uploaded and saved twice, are the disadvantages of this approach. It's quicker to implement though

I was looking for solution for some time and eventually wrote app django-resized.

The following code uses the PIL Engine (part of sorl-thumbnail) to crop an image called picture.jpg (tested using Python 3.8 and sorl-thumbnail==12.6.3):
#
# Change this import to get the Engine of your underlying libraries.
# Options are: convert_engine, pgmagick_engine, pil_engine, vipsthumbnail_engine or wand_engine.
#
from sorl.thumbnail.engines.pil_engine import Engine
# This object has all we need
engine = Engine()
#
# When receiving data from a request
# you probably have a BytesIO instance ready to use like:
#
# im = engine.get_image(my_bytes_io)
#
with open("picture.jpg", "rb") as f:
im = engine.get_image(f)
im_crop = engine.crop(im, (535, 535), options={'crop': 'smart'})
im_crop.save("picture-thumb.jpg")
Instead of modifying the save method, I would have a helper function for reducing the image size (with the lines above) and call it from the Django view or form before updating the image field. Though it would work doing it on the save itself.
On the other hand, the Engine API has more useful features that could be useful! This API has been there since the first commit so, in my opinion, it's unlikely to change in the future: create, cropbox, orientation, flip_dimensions, colorspace, remove_border, calculate_scaling_factor, scale, crop, rounded, blur, padding, write, cleanup, get_image_ratio, get_image_info, get_image, get_image_size, is_valid_image.

Related

wagtail set auto focal-point during upload

I notice we can set custom image model on wagtail here: https://docs.wagtail.io/en/v2.9/advanced_topics/images/custom_image_model.html
I am trying to add an automated focal point during upload max max 200*220px.
I tried a lot following its above documentation.
from django.db import models
from wagtail.images.models import Image, AbstractImage, AbstractRendition
class CustomImage(AbstractImage):
# Add any extra fields to image here
# eg. To add a caption field:
# caption = models.CharField(max_length=255, blank=True)
admin_form_fields = Image.admin_form_fields + (
# Then add the field names here to make them appear in the form:
# 'caption',
)
class CustomRendition(AbstractRendition):
image = models.ForeignKey(CustomImage, on_delete=models.CASCADE, related_name='renditions')
class Meta:
unique_together = (
('image', 'filter_spec', 'focal_point_key'),
Can anyone please help me get it done setting up custom focal point?
Thanks
Anamul
You probably do not need a custom image model to achieve this goal, Django has a built in system called signals. This lets you listen to creation & editing (plus others) of any existing Django model and modify the data before it is saved to the DB.
A good example of this in use already in Wagtail is the feature detection system, which will automatically add a focal point on save if faces are detected.
You can see how this has been implemented in the source code, wagtail/images/signal_handlers.py.
You may need to understand how to build up a focal point, depending on how you want to calculate it but basically you need to call set_focal_point on your image instance. This method must be supplied an instance of a Rect which can be found in the source at images/rect.py.
It is important to understand how to call your signal handlers registration function, I found this Stack Overflow answer to be helpful. However, it might be simpler to just add it to your wagtail_hooks.py file as you know it will be run at the right time (when the app is ready & models are loaded.
You can read more at the Django docs for app.ready() if you would prefer not to rely on the wagtail_hooks.py approach.
Example Implementation
myapp/signal_handlers.py
from django.db.models.signals import pre_save
from wagtail.images import get_image_model
from wagtail.images.rect import Rect
def pre_save_image_add_auto_focal_point(instance, **kwargs):
# Make sure the image doesn't already have a focal point
# add any other logic here based on the image about to be saved
if not instance.has_focal_point():
# this will run on update and creation, check instance.pk to see if this is new
# generate a focal_point - via Rect(left, top, right, bottom)
focal_point = Rect(15, 15, 150, 150)
# Set the focal point
instance.set_focal_point(focal_point)
def register_signal_handlers():
# important: this function must be called at the app ready
Image = get_image_model()
pre_save.connect(pre_save_image_add_auto_focal_point, sender=Image)
myapp/wagtail_hooks.py
from .signal_handlers import register_signal_handlers
register_signal_handlers()

Manually trigger image save method in Django model

I have a Django model with a customized image field. The image field creates some thumbnail sizes on upload. Code may look like this:
from django.db import models
from utils import CustomImageField
class Photo(models.Model):
image = CustomImageField()
Now I modify the original image, let's say I rotate it. And now I want to trigger the save method of my image field again, in order to overwrite the thumbnails and create rotated versions. So, I don't need to rotate the thumbnails elsewhere in my code (DRY).
Any thoughts? Something along those lines - but how exactly?
p = Photo.objects.get(pk=1)
p.image.save(...)
I have full control over the CustomImageField widget. The save() method is defined as:
def save(self, name, path, save=True):
Question is, what do I use for the methods parameters?
This question looks like a duplicate of Programmatically saving image to Django ImageField
The parameters of the ImageField.save() method are documented for FileField.save() (of which ImageField is a subclass):
https://docs.djangoproject.com/en/1.9/ref/models/fields/#django.db.models.fields.files.FieldFile.save
Takes two required arguments: name which is the name of the file, and
content which is an object containing the file’s contents. The
optional save argument controls whether or not the model instance is
saved after the file associated with this field has been altered.
Defaults to True.
Here is what is working for us:
class CustomImage(models.Model):
image = models.ImageField(upload_to=get_file_path, max_length=500)
orig_name = models.TextField()
This is the method that adds an image file to the ImageField from an http resource:
from django.core.files.base import ContentFile
def download_photo(amazon_id, url):
img_data = requests.get(url)
img = CustomImage(orig_name=img_data.url)
img.image.save(slugify(img.orig_name), ContentFile(img_data.content), save=True)
It also works without ContentFile:
new_img = File(open(different_obj.image.path), 'r')
img.image.save(different_obj.image.url, new_img, save=True)
See also:
- https://docs.djangoproject.com/en/1.9/topics/files/
- https://djangosnippets.org/snippets/2587/
An option is dirty field checking, either manually (see this SO question) or using a pypi package
Alternatively, if you want to conserve memory, the resizing can be triggered from the field's property setter (assuming you inherit from FileField
class CustomImageField(FileField):
def _set_file(self, file):
has_file_changed = file != self._file
super(CustomImageField, self)._set_file(file)
if has_file_changed:
self.handle_resizing_etc()
# need to redeclare property so it points to the right _set_file
file = property(FileField._get_file, _set_file, FileField._del_file)
disclaimer: I haven't used this approach in production code and I haven't written a proof of concept before posting this answer, so it might not work as desired

Remove all thumbnails generated with easy-thumbnails Django App

I am using easy-thumbnails in my Django 1.5 project to generate thumbnail images.
I have been using several different sizes for thumbnails for testing, but now I would like to clear all thumbnails from my filesystem and from the easy-thumbnails database entries. Over time I created several different sizes of many images and I would like to remove those now.
My intention is to start with a clean slate and to remove all thumbnail images. I could not find out how to do that.
Just had the same problem.
Given:
class MyModel(Model):
image = ThumbnailerImageField()
You can delete all thumbnails with:
for m in MyModel.objects.all():
m.image.delete_thumbnails()
If you instead have:
class MyModel(Model):
image = ImageField()
Then you should use:
from easy_thumbnails.files import get_thumbnailer
for m in MyModel.objects.all():
thumbnailer = get_thumbnailer(m.image)
thumbnailer.delete_thumbnails()
I have created a Picture model in which I added a method, as follows
from easy_thumbnails.models import Source, Thumbnail
def clean_thumbnail(self):
if self.image:
sources = Source.objects.filter(name=self.image.name)
if sources.exists():
for thumb in Thumbnail.objects.filter(source=sources[0]):
try:
os.remove(os.path.join(settings.MEDIA_ROOT, thumb.name))
thumb.delete()
except Exception, e:
logger.warning(e)
And it works like a charm.

Handling files in Django

I'm using ImageMagick and the binding wand to generate thumbnails for uploaded images in Django. I can generate the thumbnail fine, but I'm uncertain about how to go about passing the image object from ImageMagick back into the Django model. So I have a simplified model as below:
from wand import Image
class Attachment(models.Model):
attachment = models.FileField(upload_to="some_path")
thumbnail = models.ImageField(upload_to="other_path")
def generate_thumb(self):
with Image(file=self.attachment) as wand:
thumb = wand.resize(width=50, height=50)
thumb.save(file=self.thumbnail)
This generates an error at the last line of ValueError: The 'thumbnail' attribute has no file associated with it. Is there a simple way to get a file object out of wand and into django without too much silliness?
Thanks.
Disclaimer: I am the creator of Wand you are trying to use.
First of all, ImageField requires PIL. It means you don’t need Wand, because you probably already installed an another image library. However I’ll answer to your question without any big changes.
It seems that self.thumbnail was not initialized yet at the moment, so you have to create a new File or ImageFile first:
import io
def generate_thumb(self):
buffer = io.BytesIO()
with Image(file=self.attachment) as wand:
wand.resize(width=50, height=50)
wand.save(file=buffer)
buffer.seek(0)
self.thumbnail = ImageFile(buffer)
Plus, from wand import Image will raise ImportError. It should be changed:
from wand.image import Image
If the goal is easy thumbnails in your django app try: https://github.com/sorl/sorl-thumbnail
Its pretty popular and active.

Django - FileField and images

I'm trying to create some kind of 'media manager' model which will allow the user to upload different kings of media (images, swfs, pdfs) similar to the way WordPress does. My media model looks something like this:
class Media(models.Model):
id = models.AutoField(primary_key=True)
url = models.FileField(upload_to="uploads")
mimetype = models.CharField(max_length=64, editable=False)
created = models.DateTimeField(auto_now_add=True, editable=False)
When a user uploads a file, I want to first determine what kind of file it is and if it's an image, manipulate it further. I want to be able to to specify the dimensions (crop) of the uploaded image via a view, so when I call the .save() method, the model will resize and crop the image, upload it and populate the database with the url to the file.
I also want to ensure that the upload of the image is done AFTER the post processing (cropping etc), I have no need to keep the original file.
So the question I am asking is how do I got about passing parameters to the FileFields save method (so I can pass dynamic properties for image post processing) and how can I ensure the post processing is done BEFORE the image is uploaded?
Edit: When I say before the image is uploaded, I mean before it's saved to it's final destination. I understand the image has to go int othe tmp folder first before I can post process it. Sorry for the misleading question.
Hope someone can help :)
You cannot do anything before the image is uploaded (because you have nothing to work with).
But if you want modify the image before saving it into db, you can do it in model's save() method, before calling parent's save()
If you are uploading it via admin, override method save_model() in admin.py, ie:
def save_model(self, request, obj, form, change):
file = request.FILES.get('url') # name of field
if file:
# proceed your code
return super(AdminClassName, self).save_model(request, obj, form, change)
Here is my code how to change file before actually upload it. I think you should get my idea
from django.core.files.uploadedfile import InMemoryUploadedFile
#....
#some form
def clean_avatar(self):
av = self.cleaned_data['avatar']
resized = make_avatar(av,65) # My custom function than returns image
return InMemoryUploadedFile(resized, av.field_name, av.name, av.content_type, resized.len, av.charset)
You can read django code for InMemoryUploadedFile "documentation".
And in your resize/crop function you should use StringIO not file to save result
How could the processing be done before the image is uploaded? That doesn't make sense. The server doesn't have any access to the file until you upload it.
If you actually want to handle the file before it's saved, you can write a custom upload handler. You can test there whether the file is an image, then crop it appropriately, before saving. (You'll need the Python Imaging Library for both of those tasks.)