#8204 closed (wontfix)
FileSystemStorage should make it easy to save other types of files
| Reported by: | Julien Phalip | Owned by: | nobody |
|---|---|---|---|
| Component: | Uncategorized | Version: | dev |
| Severity: | Keywords: | ||
| Cc: | flori@… | Triage Stage: | Unreviewed |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | yes |
| Easy pickings: | no | UI/UX: | no |
Description
It is quite common to use PIL to modify an uploaded image, e.g. create a thumbnail:
from PIL import Image
uploaded_image = Image.open(StringIO.StringIO(uploaded_file.read()))
... # Modify image, e.g. create thumbnail
uploaded_image.save('/blah/image.gif')
thumbnail_image.save('/blah/image_thumbnail.gif')
Now that the new file storage system has been merged in trunk, I could not find any easy way to use FileSystemStorage for saving PIL images. The problem is that FileSystemStorage assumes that it's given InMemoryUploadedFile or TemporaryUploadedFile (for example).
It seems quite essential that there should be a fall back option to manage other kinds of file content.
Attachments (4)
Change History (21)
by , 17 years ago
| Attachment: | 8204.storage_save.diff added |
|---|
comment:1 by , 17 years ago
| milestone: | → 1.0 maybe |
|---|
1.0 maybe... although I think this *should* be tackled in 1.0. Otherwise, there might be many requests from people manipulating images with PIL.
by , 17 years ago
| Attachment: | 8204.storage_save.2.diff added |
|---|
First check if the existence of a 'save' method. Raise an exception if content type not recognized.
comment:2 by , 17 years ago
| Cc: | added |
|---|
follow-up: 4 comment:3 by , 17 years ago
Since you don't know whether every "save"-method of any kind of objects would expect a path as an argument we should check whether the instance/content is a PIL image or not. Calling save with the full path could end in mysterious bugs or behaviour when using other kinds of objects than PIL Images or *File-Objects, which also offers save-methods.
comment:4 by , 17 years ago
| Patch needs improvement: | set |
|---|
Replying to flosch:
Since you don't know whether every "save"-method of any kind of objects would expect a path as an argument we should check whether the instance/content is a PIL image or not. Calling save with the full path could end in mysterious bugs or behaviour when using other kinds of objects than PIL Images or *File-Objects, which also offers save-methods.
I totally agree with that. In fact, in that patch I've simply replicated the test for if hasattr(content, 'temporary_file_path').
To be safe, you'd need to do isinstance (or something like that) checks to be sure that you're dealing with the right classes of objects.
comment:6 by , 17 years ago
| milestone: | 1.0 maybe → 1.0 |
|---|
Following on the example given in the description, what would be really nice to be able to do is:
from PIL import Image
from django.core.files.storage import FileSystemStorage
storage = FileSystemStorage('/myimages')
uploaded_image = Image.open(StringIO.StringIO(uploaded_file.read()))
thumbnail_image = ... # Create thumbnail
storage.save('/blah/image.gif', uploaded_image)
storage.save('/blah/image_thumbnail.gif', thumbnail_image)
comment:7 by , 17 years ago
I encountered one problem with checking the content type against a PIL image: If you want do so, you're forced to import the PIL image which makes django requiring PIL in general. I don't know wheter PIL Image objects offering a version-string or something we can check; that would IMO solve the problem. Otherwise we can catch any ImportError while importing PIL and including the result of importing in the content type detection.
by , 17 years ago
| Attachment: | filestorage.diff added |
|---|
maybe this will work, cannot test it yet, comments?
comment:8 by , 17 years ago
| Needs tests: | set |
|---|
comment:9 by , 17 years ago
While I'm not categorically opposed to supporting PIL images as valid file types, I'm afraid we're going about this all wrong at the moment. There's no reason for Storage (or any subclass of it) to know anything about PIL. If anything, what we'd need instead is a new File object that can handle PIL images and feed the file data to Storage.
Looking at the documentation, it seems like this could also be approached from the opposite direction. Rather than feeding a PIL image to Storage, it should be possible to pass a File to Image.save(). Then, File would do what it needs to in order to commit the file to the storage system behind the scenes.
I'm afraid I don't use PIL personally, so I'm not the best person to try out some code for it. Could somebody who needs this give that a shot and let me know what problems are encountered? If it works the way I think it will, we shouldn't even need this ticket.
comment:10 by , 17 years ago
| Needs tests: | unset |
|---|
comment:11 by , 17 years ago
I agree that the current approach is not ideal, including the test if hasattr(content, 'temporary_file_path'). Ideally, all tests should be done with isinstance and the PIL image content should be wrapped up in a Django File-like object, so FileSystemStorage would manipulate all files in a consistent way.
I'll have a look at how to approach that.
comment:12 by , 17 years ago
I tested my patch and it works for me. Even though I agree with you, Gulopine.
by , 17 years ago
| Attachment: | filestorage.2.diff added |
|---|
I extended ContentFile for usage with PIL or others .. example follows, works for me (dunno whether ContentFile is the right place for that?)
comment:13 by , 17 years ago
Works for me with like the following way:
from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage
from PIL import Image
uploaded_image = Image.open(StringIO.StringIO(uploaded_file.read()))
thumbnail_image = uploaded_image.resize((100,100)) # create the thumbnail
cf = ContentFile()
thumbnail_image.save(cf, format = "gif")
storage.save('static/image.gif', uploaded_file)
storage.save('static/image_thumbnail.gif', cf)
comment:14 by , 17 years ago
That still seems like a lot more work than is really necessary here, but it does prove that there's no need to change core to make this happen. Using that example as a base, I stripped out a lot of the unnecessary reads and copies, coming up with the following, which also works:
from PIL import Image from django.core.files.storage import default_storage uploaded_image = Image.open(uploaded_file) # No need to use StringIO thumbnail_image = uploaded_image.resize((100, 100)) # Create the thumbnail # Save the original default_storage.save('static/image.gif', uploaded_file) # Open a new file to write to output = default_storage.open('static/image_thumbnail.gif', 'wb') thumbnail_image.save(output, format='gif') output.close() # Ensures that it writes to the file
There's no need to read the file into StringIO, nor is there a need to create a temporary ContentFile before saving to the Storage object. File and its various subclasses were designed to work just like files in as many ways as possible, so that things like this can work without having to worry about all those finicky little details.
Given that there are now multiple demonstrated ways to achieve this, the only thing that's left to consider is whether a shortcut for thumbnailing should be included in core. I'm not convinced it should be, but I'll hold off on issuing a wontfix until I can think it a bit further, and see if anyone convinces me.
comment:15 by , 17 years ago
| Resolution: | → wontfix |
|---|---|
| Status: | new → closed |
No, since File is sufficiently file-like, there's no good reason to make Storage adopt domain-specific behavior. And thumnailing, though common, isn't really appropriate to be part of core.
comment:16 by , 17 years ago
Cool, that's great news. I still believe it's worth a small section in the doc. As you say, thumbnailing is pretty common, and a small example like the one above given by Gulopine would prevent many questions popping up on the forums.
Adding a fall back option for other kinds of file contents