Django - edit uploaded file Before final saving - python

i'm making a site for uploading and processing of files (mostly images but also other stuff). sometimes, some files need some editing before saving.
i want edit the uploaded file before saving it to media dir (mostly to avoid IO interaction and only keep original file in memory and discard after saving edit version).
edit: so i thought i have to run the edit function in POST, and change information in request.FILES. but i failed, and don't know what to do anymore.
NOTE: i'm mostly looking for a way to do it via class views.
here's some code for reference:
The model:
class FilePatient(models.Model):
file_imag = models.FileField(upload_to='')
The View:
class FileAddView(LoginRequiredMixin, UserPassesTestMixin, SuccessMessageMixin, CreateView):
model = FilePatient
fields = ['file_imag']
def post(self, request, *args, **kwargs):
if request.FILES['file_imag'].name.endswith('.png'):
newFile=editFile(request.FILES['image_imag'])
# what to do, what not to do
return super().post(request, *args, **kwargs)

I hope the following will help you... this code "edits" the file by resizing the original file if it is too big. Based on working code, I substituted the vars you used in your question, not tested as below.
from pathlib import Path
import PIL
from six import BytesIO
from django.core.files.uploadedfile import InMemoryUploadedFile
img = request.FILES['fileInput']
if img.name.endswith('.png'):
fp = FilePatient.objects.get(...) # get the object for uploading file
img2 = PIL.Image.open(img)
exif = img2.info['exif'] if 'exif' in img2.info else None
max_hw = 800 # px width / height maximum
width_percent = (max_hw/float(img2.size[0]))
heigth_percent = (max_hw/float(img2.size[1]))
min_wh_percent = float(min(width_percent, heigth_percent))
if min_wh_percent <= 1:
width_size = int((float(img2.size[0])*min_wh_percent))
height_size = int((float(img2.size[1])*min_wh_percent))
img2 = img2.resize((width_size, height_size), PIL.Image.ANTIALIAS)
fp.file_imag = img2
buffer = BytesIO()
if exif:
img2.save(buffer, format='JPEG', exif=exif, quality=90)
else:
img2.save(buffer, format='JPEG', quality=90)
buffer.seek(0)
fp.file_imag.name = Path(img.name).stem
fp.file_imag = InMemoryUploadedFile(buffer,
'ImageField',
f"{fp.file_imag.name}.jpg",
'image/jpeg',
img2.size,
"utf-8")
fp.save()

Related

Unable to save webp file to model field

I'm not sure if this is entirely django related, but if someone could help me, that would be so much appreciated! I'm having trouble generating a webp file from the following code
from io import BytesIO
from PIL import Image
import requests
I've got the following model
class UserImage(models.Model):
user_provided_image = VersatileImageField(upload_to=folder10, null=True, blank=True)
nextgen_image = models.FileField(upload_to=folder10,null=True, blank=True) #for WebP images
I'm creating a webp file. This code works, but it saved it to the file to the root directory of my project and I'm not sure how to save it to the FileField (i.e. nextgen_image ) on my model
def create_webp_image(sender, instance, *args, **kwargs):
image_url = instance.image.thumbnail['1920x1080'].url
try:
response = requests.get(image_url, stream=True)
path = image_url
except: #local env
path = "http://localhost:8000" + image_url
response = requests.get(path, stream=True)
img = Image.open(BytesIO(response.content))
#build file path
position = path.rfind("/") + 1
newpath = path[0:position]
#build file name
image_name = path[position:]
name_of_file = image_name.split('.')[0] + ".webp"
#this creates the webp file
img.save(name_of_file,"webp")
#save image to model
#instance.nextgen_image = ?
post_save.connect(create_webp_image, sender=UserImage)
Thanks!
You can use something like that:
from django.core.files.base import ContentFile
...
img_content = ContentFile(BytesIO(response.content))
instance.nextgen_image.save(name_of_file, img_content, save=True)
...
If you want to use packages to get the job done then use this package django-resized.
Based on the code provided above this should do the trick. Hoping that this will solve your issue. Cheers
nextgen_image = ResizedImageField(force_format="WEBP",
upload_to=folder10,null=True, blank=True)

Pillow Resizing images (thumbnail) on Amazon S3 - Django

I am try resize an image when this is saved.
Specifically, my images are stored on Amazon S3, then I use the django-storages and boto3 third party applications
When a image is saved, this is stored in my Amazon S3 bucket, having a acces url such as follow:
https://s3-sa-east-1.amazonaws.com/ihost-project/media/studyoffer_images/algoritmos-para-ensenanza/15061122523583.jpg
The code to save and resize the image is this:
class UploadStudyOffer(models.Model):
study_offer = models.ForeignKey(StudiesOffert, related_name='uploadsstudyoffer')
image = models.ImageField(upload_to=get_image_path)
# images folder per object
def save(self, *args, **kwargs):
super(UploadStudyOffer, self).save(*args, **kwargs)
# We first check to make sure an image exists
if self.image:
# Open image and check their size
image = Image.open(self.image)
i_width, i_height = image.size
max_size = (100,100)
# We resize the image if it's too large
if i_width > 1000:
image.thumbnail(max_size, Image.ANTIALIAS)
image.save(self.image.path)
When I upload an image, I get this message:
Exception Type: NotImplementedError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: This backend doesn't support absolute paths.
And I am not sure, if the error is manage at storages or boto backends or in Pillow.
Then at level of Pillow I found the following options in the moment of save the image, such as follow:
I change the section code:
image.save(self.image.path)
to:
image.save(self.image.name)
And I get this error:
File "/home/bgarcial/workspace/hostayni_platform/hosts/models.py" in save
542. image.save(self.image.name) #[Errno 2] No such file or directory: 'studyoffer_images/ingenieria-de-sistemas/15061122523583.jpg'
File "/home/bgarcial/.virtualenvs/hostayni/lib/python3.6/site-packages/PIL/Image.py" in save
1725. fp = builtins.open(filename, "w+b")
Exception Type: FileNotFoundError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: [Errno 2] No such file or directory: 'studyoffer_images/algoritmos-para-ensenanza/1900-X-1080-Wallpapers-022.jpg'
Of course, my image is stored om Amazon S3 and not locally in my project or hard disk, then I use the url parameter of this way:
I change
image.save(self.image.name)
to
image.save(self.image.url)
And I get this error:
Exception Type: FileNotFoundError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: [Errno 2] No such file or directory: 'https://s3-sa-east-1.amazonaws.com/ihost-project/media/studyoffer_images/algoritmos-para-ensenanza/15061122523583.jpg'
Getting the amazon s3 image URL does not works, even though the url is a valid url https://s3-sa-east-1.amazonaws.com/ihost-project/media/studyoffer_images/algoritmos-para-ensenanza/15061122523583.jpg
Then I change
image.save(self.image.url)
to:
image.save(self.image.file)
And my image is uploaded without any errors, but is not resized and is uploaded as its original format.
How to can I process a image uploaded from my application and their result was saved on Amazon S3 to after use them?
Fairly new to Django/Python, but this is how I solved it. Save large file to AWS, open it with Pillow, then resize and save in memory. Then push to AWS using default_storage just as the django-storages help docs suggest. Note that img.thumbnail will retain aspect with just the longer edge of the image set to 1000 pixels. image is the Django model ImageField.
from django.core.files.storage import default_storage
from io import BytesIO
..
def save(self, *args, **kwargs):
#run save of parent class above to save original image to disk
super().save(*args, **kwargs)
memfile = BytesIO()
img = Image.open(self.image)
if img.height > 1000 or img.width > 1000:
output_size = (1000, 1000)
img.thumbnail(output_size, Image.ANTIALIAS)
img.save(memfile, 'JPEG', quality=95)
default_storage.save(self.image.name, memfile)
memfile.close()
img.close()
You can make it easier and use easy_thumbnails app.
If you want to crop the image on save then you can do it with:
from easy_thumbnails.fields import ThumbnailerImageField
CROP_SETTINGS = {'size': (1000, 500), 'crop': 'smart'}
class UploadStudyOffer(models.Model):
image =ThumbnailerImageField(upload_to=get_image_path,
resize_source=CROP_SETTINGS)
Or you can manually specify the size of the image in the template:
{% load thumbnail %}
<img src="{% thumbnail offer.image 1000x500 crop %}" alt="" />
Most of what I found online suggested removing the Pillow resize and writing an AWS Lambda function to handle the resize on upload. I initially tried that approach, but according to the AWS docs you shouldn’t use the same bucket for input and output, meaning I had to create a second S3 bucket just for resized images. I couldn’t figure out how to get that setup working with django-storages.
A second approach I found mentioned using a buffer to save the resized image into, and then saving that to AWS. The examples of this that I found were either incomplete or used old versions of python. Here is what actually worked for me.
user/models.py
from app.utils import image_resize
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default="profile-default.png",
upload_to="profile_pics")
def __str__(self):
return f"{self.user.username} Profile"
def save(self, *args, **kwargs):
image_resize(self.image, 512, 512)
super().save(*args, **kwargs)
app/utils.py
from django.core.files import File
from pathlib import Path
from PIL import Image
from io import BytesIO
image_types = {
"jpg": "JPEG",
"jpeg": "JPEG",
"png": "PNG",
"gif": "GIF",
"tif": "TIFF",
"tiff": "TIFF",
}
def image_resize(image, width, height):
# Open the image using Pillow
img = Image.open(image)
# check if either the width or height is greater than the max
if img.width > width or img.height > height:
output_size = (width, height)
# Create a new resized “thumbnail” version of the image with Pillow
img.thumbnail(output_size)
# Find the file name of the image
img_filename = Path(image.file.name).name
# Spilt the filename on “.” to get the file extension only
img_suffix = Path(image.file.name).name.split(".")[-1]
# Use the file extension to determine the file type from the image_types dictionary
img_format = image_types[img_suffix]
# Save the resized image into the buffer, noting the correct file type
buffer = BytesIO()
img.save(buffer, format=img_format)
# Wrap the buffer in File object
file_object = File(buffer)
# Save the new resized file as usual, which will save to S3 using django-storages
image.save(img_filename, file_object)
I’m overriding the save method still, and calling a function I’ve placed in utils.py of my main application. The following happens in the image_resize function:
The image_function checks if the image is too wide or tall and, if it is, saves a resized version first to a memory buffer and then to S3. Back in the save method we call super().save() to save the remaining fields. The super().save() needs to be called after the image.save() or both the original and the resized images will get uploaded to S3.
I hope that was helpful to someone.
You can Visit https://blog.soards.me/posts/resize-image-on-save-in-django-before-sending-to-amazon-s3/ for more details

How to get image from response and save it in models.ImageField

Asking this I realize that topic is quite common and perhaps was discussed many times. But it still not clear for me, so I'm trying to get image file from response, resize it and save it in model. I deal with the error:
AttributeError at /saveimage/
'InMemoryUploadedFile' object has no attribute 'get'
my code is like so:
import Image
import cStringIO
from django.core.files.uploadedfile import InMemoryUploadedFile
def get_and_save_image():
file = cStringIO.StringIO()
size = (200,200)
color = (255,0,0,0)
image = Image.new("RGBA", size, color)
image.save(file, format='JPEG')
image_file = InMemoryUploadedFile(file, None, 'foo.jpg', 'jpeg', None, None)
image_file.seek(0)
return image_file
Http response:
FILES:
some_image = <InMemoryUploadedFile: image.jpg (image/jpeg)>
Is it the proper way to save image into ImageField? Thanks in advance.
Here are two ways you can do this:
Save the image in a models.ImageField, and then return the image's url in the response, and you can create the image element in javascript and add the url.
Encode the image's data in base64 and return it as a string, which you can easily use in the front end. Here's an example of this:
Backend:
from PIL import Image
from io import BytesIO
import base64
def get_and_save_image():
size = (200,200)
color = (255,0,0,0)
image = Image.new("RGBA", size, color)
img_buffer = BytesIO()
image.save(img_buffer, format='JPEG')
response_string = base64.b64encode(img_buffer.getvalue())
return response_string
Front end:
<img src="data:image/jpg;base64,{{ THE_RESPONSE_STRING }}">

Imagekit - cache image not deleted after deleting original

In my project I have a model where I use Imagekit to process an image. When I save an image I have following requirements:
rename image and thumbnail to a unique name
when a new image is loaded, the old one should be removed (and the thumbnail in the cache should refresh to the new image).
To accomplish this, I use following code:
The model:
def generate_cache_filename(instance, path, specname, extension):
extension = '.jpg'
return 'cache/images_upload/%s_%s%s' % (instance.pk, specname, extension)
def generate_image_filename_1(instance, filename):
filename = '1'
extension = '.jpg'
return 'images_upload/%s_%s%s' % (instance.pk, filename, extension)
class Model(models.Model):
name = models.CharField(max_length=40)
image_1 = ProcessedImageField([Adjust(contrast=1.2, sharpness=1.1), ResizeToFill(500, 370)], upload_to=generate_image_filename_1, format='JPEG', options={'quality': 90})
thumbnail_1 = ImageSpec([Adjust(contrast=1.2, sharpness=1.1), ResizeToFill(83, 78)], image_field='image_1', cache_to=generate_cache_filename, format='JPEG', options={'quality': 90})
The form (to delete the image when it is replaced by a new one):
if form.is_valid():
form_image = form.cleaned_data['image_1']
try:
details = Model.objects.get(pk=pk)
if details.image_1 != form_image:
details.image_1.delete(save=False)
except Model.DoesNotExist:
pass
form.save()
The part of renaming the images and replacing image_1 (= loading new and deleting old) works just fine. But for some reason the thumbnail_1 in the cache does not refresh (= is still the thumbnail of the old image).
I think it has something to do with the deletion code in the form, but I can't figure out why and how to solve it. Someone with suggestions?
UPDATE 1: it has also something to do with the 'renaming'. I did some extra tests: when I don't rename the image_1 file, then everything works fine (also refreshing of the thumbnail). But when I load another image with the same name, then I have the same problem: image_1 is updated, but thumbnail_1 is still the thumbnail of the old image.
UPDATE 2: did some more tests and when uploading a new image with the same filename, I definitely enter the if statement in try. So the old image is deleted. According to the documentation of Imagekit, the thumbnail should also be deleted. But this is not the case.
Many thanks!
In the mean time I found a working solution.
The main reason why the above code didn't work was because of the thumbnail in the cache that was not deleted after deleting the original image, especially in cases where the new image had the same filename as the previous one => then the thumbnail was never deleted.
=> I still don't know why..., because I expected that the cached image is always deleted when the original is deleted.
Using the following code everything works as expected:
Basically I made sure the new uploaded image has always another filename:
Model.py
def generate_cache_filename(instance, path, specname, extension):
extension = '.jpg'
return 'cache/images_upload/%s_%s%s' % (instance.pk, specname, extension)
# generate random string of 10 characters
def id_generator(size=10, chars=string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for x in range(size))
def generate_random_filename(instance, filename):
filename = id_generator()
extension = '.jpg'
return 'images_upload/%s_%s%s' % (instance.pk, filename, extension)
class Model(models.Model):
name = models.CharField(max_length=20)
image_1 = ProcessedImageField([Adjust(contrast=1.2, sharpness=1.1), ResizeToFill(500, 370)], upload_to=generate_random_filename, format='JPEG', options={'quality': 90})
thumbnail_1 = ImageSpec([Adjust(contrast=1.2, sharpness=1.1), ResizeToFill(83, 78)], image_field='image_1', cache_to=generate_cache_filename, format='JPEG', options={'quality': 90})
View.py:
# thanks to jdi for the help in the for-loop
if form.is_valid():
# A for loop is used here, because my database contains 5 images and 5 thumbnails
image_list = ['image_%d' % i for i in xrange(1,6)]
for image_name in image_list:
form_image = form.cleaned_data[image_name]
try:
details = Model.objects.get(pk=pk)
if getattr(details, image_name, None) != form_image:
getattr(details, image_name, None).delete(save=False)
except Model.DoesNotExist:
pass
Hopefully this can help out others as well.
Kind Regards

changing file name in django

I have a this model...
class MyModel(models.Model):
...
file = models.FileField(upload_to='files/',null=True, blank=True)
...
when i upload a file, example file name is docfile.doc. when i change the file or i rewrite it and upload again docfile.doc the file will become docfile_1.doc and the old docfile.doc is still exist.
i am doing the uploading and saving data in django-admin
my question is, how can i remove the old docfile.doc if i upload the new docfile.doc and the file name is still docfile.doc?
can anyone help me in my case? thanks in advance
i try this one :
def content_file_name(instance, filename):
print instance
print filename
file = os.path.exists(filename)
print file
if file:
os.remove(filename)
return "file/"+str(filename)
class MyModel(models.Model):
...
file = models.FileField(upload_to=content_file_name,null=True, blank=True)
...
but nothing happend, when i upload docfile.doc again, it will become docfile_1.doc and the old docfile.doc still exist.
i got it... i use this
def content_file_name(instance, filename):
print instance
print filename
file = os.path.exists("media/file/"+str(filename))
print file
if file:
os.remove("media/file/"+str(filename))
return "file/"+str(filename)
I don't know exactly how to do it, but i think these links can help you:
Here you can find the two options that a FileField accept. The one that i think will interest you the most is FileField.storage. You can pass a storage object in that parameter.
It says:
FileField.storage: Optional. A storage object, which handles the storage and retrieval of your files.
Then, if you read this you would see that you can write your own storage object. Here is some explanation on how to do it. I think that you could just override the _save method in order to accomplish what you want to do (i.e: if the file already exists, remove it before saving the new copy.)
But be careful! I don't know which is the source of the files you are going to store. Maybe, your app is going to recieve lots of files with the same name, although they are all different. In this case, you would want to use a callable as the FileField.upload_to parameter, so that determine a unique filename for each file your site recieve.
I hope this helps you!
You could also have a look here: ImageField overwrite image file with same name
Define your own storage and overwrite its get available_name method.
The next code solves your problem. You override pre_save method where image is actually saved to storage. Please, rename functions for your project. Use newly created image field ImageFieldWithPermantName with your upload_to function (content_file_name).
If the code is too complicated you could simplify it. I use the code to do more complex operations for uploading images: I create thumbnails on-the-fly in custom _save_image function. So, you can simplify it.
from PIL import Image
import StringIO
from django.db.models import ImageField
from django.db.models.fields.files import FileField
from dargent.settings import MEDIA_ROOT
import os
class ImageFieldWithPermanentName( ImageField ):
def pre_save( self, model_instance, add ):
file = super( FileField, self ).pre_save(model_instance, add)
if file and not file._committed:
if callable( self.upload_to ):
path = self.upload_to( model_instance, "" )
else:
path = self.upload_to
file.name = path # here we set the same name to a file
path = os.path.join( MEDIA_ROOT, path )
chunks = _get_chunks( file.chunks() )
_save_image( chunks, path )
return file
def _get_chunks( chunks ):
chunks_ = ""
for chunk in chunks:
chunks_ += chunk
return chunks_
def _get_image( chunks ):
chunks_ = ""
for chunk in chunks:
chunks_ += chunk
virt_file = StringIO.StringIO( chunks_ )
image = Image.open( virt_file )
return image
def _save_image( chunks, out_file_path ):
image = _get_image( chunks )
image.save( out_file_path, "JPEG", quality = 100 )

Categories

Resources