I have this code,
#receiver(post_save, sender=FileAnswer)
def save_category_signals(sender, instance, **kwargs):
file_types = ["image", "image-multiple"]
file_type = instance.question.form_input_type
if file_type in file_types:
image_path = instance.body.path
image = Image.open(image_path)
img = image.convert('RGB')
new_path = image_path.rsplit('.', 1)
pdf = img.save(f'{new_path[0]}_{instance.id}.pdf', format="PDF")
# os.remove(image_path)
instance.body = pdf
instance.save()
# os.remove(image_path) # remove old image
This code does not associate the file to the model instance. I have looked at django ContentFile and File. still can't really wrap my head around it as the examples aren't that helpful.
try something like this you should use InMemoryUploadedFile:
#receiver(post_save, sender=FileAnswer)
def save_category_signals(sender, instance, **kwargs):
file_types = ["image", "image-multiple"]
file_type = instance.question.form_input_type
if file_type in file_types:
image_path = instance.body.path
new_path = image_path.rsplit('.', 1)
image = Image.open(image_path)
image = image.convert('RGB')
output = io.BytesIO()
image.save(output, format='PDF')
output.seek(0)
pdf = InMemoryUploadedFile(output, 'FileField',
f'{new_path[0]}_{instance.id}.pdf',
'application/pdf',
sys.getsizeof(output), None)
# os.remove(image_path)
instance.body = pdf
instance.save()
# os.remove(image_path) # remove old image
Related
I'm trying to get image uploads to also save as thumbnails, which works. The problem was when I did an update the custom method saved the thumbnail again in a different directory. so I modified my save function to look like this.
models.py
class Photo(models.Model):
title = models.CharField(max_length=64)
description = models.CharField(max_length=255)
created = models.DateTimeField(auto_now_add=True)
image = models.ImageField(upload_to='photos/%Y%m')
thumbnail = models.ImageField(blank=True, upload_to='thumbnails/%Y%m')
submitter = models.ForeignKey(get_user_model(), on_delete=models.CASCADE)
year = models.ForeignKey(Year, blank=True, on_delete=models.CASCADE)
people = TaggableManager(through=TaggedPeople, verbose_name='People')
tags = TaggableManager(through=TaggedGeneric, verbose_name='Tags')
def save(self, *args, **kwargs):
try:
this = Photo.objects.get(id=self.id)
if this.thumbnail != self.thumbnail:
this.thumbnail.delete(save=False)
except:
if self.thumbnail:
img = Image.open(BytesIO(self.thumbnail.read()))
if hasattr(img, '_getexif'):
exif = img._getexif()
if exif:
for tag, label in ExifTags.TAGS.items():
if label == 'Orientation':
orientation = tag
break
if orientation in exif:
if exif[orientation] == 3:
img = img.rotate(180, expand=True)
elif exif[orientation] == 6:
img = img.rotate(270, expand=True)
elif exif[orientation] == 8:
img = img.rotate(90, expand=True)
img.thumbnail((360,360), Image.ANTIALIAS)
output = BytesIO()
img.save(output, format='JPEG', quality=95)
output.seek(0)
self.thumbnail = File(output, self.thumbnail.name)
return super().save(*args, **kwargs)
def __str__(self):
return self.title
and my views
class PhotoCreateView(LoginRequiredMixin, CreateView):
model = Photo
fields = ['image', 'title', 'description', 'year', 'people', 'tags']
template_name = 'photoapp/create.html'
success_url = '/photo/?page=1'
extra_context = {'tags':GenericTag.objects.all().order_by('name'),'people':PeopleTag.objects.all().order_by('name'),}
def form_valid(self, form):
form.instance.thumbnail = self.request.FILES['image']
form.instance.submitter = self.request.user
return super().form_valid(form)
class PhotoUpdateView(LoginRequiredMixin, UpdateView):
template_name = 'photoapp/update.html'
model = Photo
fields = ['title', 'description', 'year', 'people', 'tags']
success_url = '/photo/?page=1'
So the CreateView now works fine, and I have stopped the duplicate thumbnail files, but the UpdateView does not work. How can I fix this?
I figured it out. I just had to add a save to the try section.
def save(self, *args, **kwargs):
try:
this = Photo.objects.get(id=self.id)
if this.thumbnail != self.thumbnail:
this.thumbnail.delete(save=False)
return super().save(*args, **kwargs)
except:
if self.thumbnail:
img = Image.open(BytesIO(self.thumbnail.read()))
if hasattr(img, '_getexif'):
exif = img._getexif()
if exif:
for tag, label in ExifTags.TAGS.items():
if label == 'Orientation':
orientation = tag
break
if orientation in exif:
if exif[orientation] == 3:
img = img.rotate(180, expand=True)
elif exif[orientation] == 6:
img = img.rotate(270, expand=True)
elif exif[orientation] == 8:
img = img.rotate(90, expand=True)
img.thumbnail((360,360), Image.ANTIALIAS)
output = BytesIO()
img.save(output, format='JPEG', quality=95)
output.seek(0)
self.thumbnail = File(output, self.thumbnail.name)
return super().save(*args, **kwargs)
I had a similar complaint in Django-Wagtail, where saving image.save() programmatically duplicated on the file system the imported image. Do get around this I used Python Glob to get the path, and OS to remove the original image after the save function had been loaded. It worked well enough for Wagtail, I wanted to capture the issue for others - as there isn't much on the web about this. It's frustrating, doubles your disk space usage due to this functionality if you aren't careful! I don't like that it renames the files/moves them, but it is what it is.
from django.core.management.base import BaseCommand, CommandError
from wagtail.images.models import Image
from django.core.files.images import ImageFile
import os
from os import path
import glob
#import sqlite3
class Command(BaseCommand):
help = "just a thing to "
def handle(self, *args, **options):
target_path = "/home/inmyth/inmyth/media/images/"
my_images = []
if os.path.exists(target_path):
my_images = glob.glob(target_path + "*.png")
for mj_imgs in my_images:
print(mj_imgs)
image_file = ImageFile(open(mj_imgs, 'rb'), name=mj_imgs[:-4])
img_label = mj_imgs.rfind("/") + 1
image = Image(title=mj_imgs[img_label:-4], file=image_file)
image.save()
os.remove(mj_imgs)
pass
I'm trying to compress the images before uploading them to the amazon s3 server, but I couldn't do it, I used 'PIL', to do it, but it didn't work
This is the code I used with the library 'PIL':
from io import BytesIO
from PIL import Image
from django.core.files import File
def compress(image):
im = Image.open(image)
im_io = BytesIO()
im.save(im_io,'PNG', quality=70)
new_image = File(im_io, name=image.name)
return new_image
class MyModel(models.Model):
file = models.FileField(blank=True, null=True, validators=[validateFileSize])
def save(self, *args, **kwargs):
new_image = compress(self.file)
self.file = new_image
super().save(*args, **kwargs)
I solved it using the following code
def compress(image):
im = Image.open(image)
# create a BytesIO object
im_io = BytesIO()
# save image to BytesIO object
#im = im.resize([500,500])
im = im.convert("RGB")
im = im.save(im_io,'JPEG', quality=70)
# create a django-friendly Files object
new_image = File(im_io, name=image.name)
return new_image
class Media(models.Model):
file = models.FileField(blank=True, null=True, validators=[validateFileSize])
def __str__(self):
return str(self.id)
def save(self, *args, **kwargs):
if self.file:
if self.file.size > (300 * 1024):
# call the compress function
new_image = compress(self.file)
# set self.image to new_image
self.file = new_image
# save
super().save(*args, **kwargs)
I am getting the following error:
'MyProfile' object has no attribute 'get_source_filename'
The following code is from a previously answered SO question, so I'm not sure what get_source_filename() is or does. A google search turned up nothing.
class MyProfile(UserenaBaseProfile):
coverpic = models.ImageField(upload_to="site_media/media/covers/", null=True, blank=True)
def save(self, *args, **kwargs):
# Did we have to resize the image?
# We pop it to remove from kwargs when we pass these along
image_resized = kwargs.pop('image_resized',False)
super(MyProfile, self).save(*args, **kwargs)
if self.coverpic:
print "yes"
basewidth = 300
filename = self.get_source_filename()
image = Image.open(filename)
wpercent = (basewidth/float(image.size[0]))
hsize = int((float(image.size[1])*float(wpercent)))
img = image.resize((basewidth,hsize), PIL.Image.ANTIALIAS)
self.coverpic = img
self.coverpic.save
I have got an image url and I want to set a ProcessedImageField attribute from it during object saving. So far I have got this:
class Video(Media):
url = models.URLField('url', max_length=256, default='')
embed_url = models.URLField('embed url', max_length=256, default='')
thumbnail = ProcessedImageField(upload_to='uploads',
processors=[ResizeToFit(width=1024, height=1024, upscale=False)],
format='JPEG',
options={'quality': 75})
def save(self, *args, **kwargs):
from django.core.files.temp import NamedTemporaryFile
import shutil
import requests
import re
params = {
'url': self.url,
'autoplay': 1,
'format': 'json',
}
try:
data = requests.get('http://www.youtube.com/oembed', params=params).json()
embed_url = re.search('src=[\'"]([^\'"]*)[\'"]', data['html']).group(1)
thumbnail_url = data['thumbnail_url']
except:
pass
response = requests.get(thumbnail_url, stream=True)
img_temp = NamedTemporaryFile(delete=True)
shutil.copyfileobj(response.raw, img_temp)
# now image data are in img_temp, how to pass that to ProcessedImageField?
super(Video, self).save(*args, **kwargs)
You should be able to save directly to that property at that point.
self.thumbnail.save("filename.ext", img_temp)
This is the resulting code (without error handling) of mine. In the end, I did in in a simpler manner by avoiding a temporary file and using ContentFile instead.
class Video(Media):
url = models.URLField('url', max_length=256, default='')
embed_url = models.URLField('embed url', max_length=256, default='')
author = models.CharField('author', max_length=64, default='', blank=True)
thumbnail = ProcessedImageField(upload_to='uploads',
processors=[ResizeToFit(width=1024, height=1024, upscale=False)],
format='JPEG',
options={'quality': 75})
def save(self, *args, **kwargs):
from django.core.files.base import ContentFile
import requests
import re
params = {
'url': self.url,
'format': 'json',
}
data = requests.get('http://www.youtube.com/oembed', params=params).json()
embed_url = re.search('src=[\'"]([^\'"]*)[\'"]', data['html']).group(1)
thumbnail_url = data['thumbnail_url']
author = data['author_name']
title = data['title']
image_data = requests.get(thumbnail_url, stream=True).raw.data
self.thumbnail.save(title, ContentFile(image_data), save=False)
self.embed_url = embed_url
self.author = author
self.title = title
super(Video, self).save(*args, **kwargs)
Ok, i've ended with this code for Python 3 :)
it has built-in retries with timeouts between them and support for downloading large files
def save_image_from_url(self, image_url):
s = requests.Session()
retries = Retry(total=5,
backoff_factor=0.1,
status_forcelist=[500, 502, 503, 504])
s.mount('https://', HTTPAdapter(max_retries=retries))
response = s.get(image_url, stream=True, timeout=9)
# here just use whatever name you want, I've just retrieve the path from my custom field
folder_name = Artist.image.field.upload_to.sub_path
random_name = uuid.uuid4().hex + ".png"
# creating folder if it doen't exist
try:
os.makedirs(os.path.join(settings.MEDIA_ROOT, folder_name))
except OSError as e:
if e.errno != errno.EEXIST:
raise
# loading image to tmp location and saving it, it's for large files because we can't handle them in memory
tmp = tempfile.NamedTemporaryFile(delete=True)
try:
tmp.write(response.raw.read())
with open(tmp.name, 'rb') as f:
self.image.save(random_name, f)
finally:
tmp.close()
Where self.image is ProcessedImageField
def upload_path_handler(instance, filename):
return filename
class SpectacleGallery(models.Model):
image = models.ImageField(upload_to=upload_path_handler)
def save(self, *args, **kwargs):
Image.open(self.image)
super(SpectacleGallery, self).save(*args, **kwargs)
When I try to open it I get:
IOError at /admin/index/spectacle/1/
cannot identify image file
Why? File is a proper image.
Does that file in save methos is not a good format for PIL?
EDIT:
Here's my final working version of code:
def save(self, *args, **kwargs):
# set paths and additional variables
self_pk = self.pk
spectacle_id = self.spectacle_id
spectacle_id_str = str(spectacle_id)
create_gallery_spectacle_dir(spectacle_id)
new_filename = generate_image_name_hash()
new_filename_main = new_filename + '.jpg'
new_filename_thumb = new_filename + '_thumb.jpg'
new_file_thumb_path = settings.SPECTACLE_GALLERY_UPLOAD_DIR + '/' + spectacle_id_str + '/' + new_filename_thumb
new_file_thumb_root_path = settings.SPECTACLE_GALLERY_UPLOAD_PATH + spectacle_id_str + '/' + new_filename_thumb
new_file_root_path = settings.SPECTACLE_GALLERY_UPLOAD_PATH + spectacle_id_str + '/' + new_filename_main
if self.image:
#set new name and thum name
self.image.name = settings.SPECTACLE_GALLERY_UPLOAD_DIR + '/' + spectacle_id_str + '/' + new_filename_main
self.image_thumb = new_file_thumb_path
# image is in form and action is add call the "real" save() method.
if self.image and not self_pk:
super(SpectacleGallery, self).save(*args, **kwargs)
# image is in form and action is edit: get old image info, create variable with image field
if self.image and self_pk:
old_img = SpectacleGallery.objects.get(pk=self.pk)
old_img_instance = old_img.image
if self.image:
if self_pk:
image = old_img_instance
else:
image = self.image
super(SpectacleGallery, self).save(*args, **kwargs) #Call the "real" save() method.
#if image in form
if self.image:
# open file with PIL and convert to RGB
tmp_file = Image.open(self.image.path)
if tmp_file.mode != 'RGB':
tmp_file = tmp_file.convert('RGB')
#create and save thumbnail
tmp_file.thumbnail(settings.SPECTACLE_GALLERY_THUMB_SIZE, Image.ANTIALIAS) #make thumbnail
tmp_file.save(new_file_thumb_root_path, 'JPEG') #save thumbnail
# if edit delete old images
if self_pk:
delete_image_and_thumb(old_img.image, old_img.image_thumb)
#open and resize original image
image = Image.open(self.image.path)
if image.mode != 'RGB':
image = image.convert('RGB')
image.thumbnail(settings.SPECTACLE_GALLERY_IMAGE_SIZE, Image.ANTIALIAS) #make thumbnail
image.save(new_file_root_path,'JPEG', quality=100)
The Django imageField isn't an image you will need to do something like this.
Image.open(self.image.path)