How to associate newly created file model - django

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

Django custom save model creating duplicate files

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

How to compress images before uploading them to Amazon s3 using django storages and django rest framework?

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)

What is 'get_source_filename()' used for in Django?

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

django imagekit: set ProcessedImageField from image url

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

Django - can't open image in model save()

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)