How to update an ImageField in Django model with a new Image - python

In my project a user can upload an image and once it is saved I can run a post_save signal to modify the image using Pillow. And after modifying the image I need to replace the existing image with the new image. Now the issue is if I run the save method in the post_save signal it will lead to maximum recursion error.So instead of this I am thinking of using update method but it throws an error regarding the characters in the file.
Below is the code:
def menuImageLocation(instance,filename):
return "%s/%s"%(instance,filename)
class restaurantMenuImage(models.Model):
restaurant = models.ForeignKey(restaurantCreateUpdate)
title = models.CharField(max_length=100,null=True,blank=True)
menuImage = models.ImageField(upload_to=menuImageLocation,null=True,blank=True,verbose_name="Menu Image")
def __unicode__(self):
return str(self.restaurant)
#receiver(post_save,sender=restaurantMenuImage)
def modifyMenuImage(sender,instance,**kwargs):
getRestaurant = restaurantCreateUpdate.objects.get(restaurantName=instance.restaurant)
image_path = instance.menuImage.path
filename = os.path.basename(image_path)
image_hold = Image.open(image_path)
image = image_hold.resize((300,300),Image.ANTIALIAS)
temp_loc = "%s/%s/%s/tmp"%(settings.MEDIA_ROOT,"menu",getRestaurant.uniqueID)
if not os.path.exists(temp_loc):
os.makedirs(temp_loc)
temp_file_path = os.path.join(temp_loc,filename)
if os.path.exists(temp_file_path):
temp_path = os.path.join(temp_loc,"%s" %(random.random()))
os.makedirs(temp_path)
temp_file_path = os.path.join(temp_path,filename)
temp_image = open(temp_file_path,"w")
image.save(temp_image,quality=80)
temp_data = open(temp_file_path,"r")
image_file = File(temp_data)
instance.menuImage.save(filename, image_file)
So if I run the last line of the code, this will result into maximum recursion error, and instead of using save method I am trying to use update method the file path but for that I am getting the error that the characters exceed the max_length of the "image_file". Will appreciate any help.

Before you run the last line, add a check if the image path of the instance is not the same as the new image path. Then the code will only run once. Your problem occurs, because the code in your post_save signal is always run when you call save on your model, which creates an endless loop. So you need to be careful when you call save in your post_save signal method.
Something like this should help:
if instance.menuImage != image_file:
instance.menuImage.save(filename, image_file)

Related

How to delete an image using default_storage.delete(file_name) in Django View?

So I have been creating a Fingerprint Matching system using Django Rest Framework. Whenever I send a fingerprint from frontend to backend to match with the stored fingerprints. The fingerprint is getting stored there which is not what I want. I want to delete it once it's use is over.
Here is the Error Image: https://i.stack.imgur.com/MCXcv.png
def post(self, request, format = None):
serializer = serializers.MatchSerializer(data = request.data)
if serializer.is_valid():
#Fetching the stored fingerprints in a different serializer.
queryset = models.Profile.objects.all()
serializer2 = serializers.ProfileSerializer(queryset, many=True)
data = serializer2.data
#Initialized a result list which will be appended with the name of the user whose
#fingerprint is matched.
result = []
file = request.FILES['fingerprint']
file_name = default_storage.save(file.name, file)
file = default_storage.open(file_name)
file_url = default_storage.url(file_name)
fImage = cv2.imread(str(file))
for i in range(len(data)):
print(data[i].get('fingerprint'))
DbImage = cv2.imread('C:/Users/ShalomAlexander/Documents/4th Year
Project/Django/FingerprintMatch/' + data[i].get('fingerprint'))
if(isMatch(fImage, DbImage)):
result.append(data[i].get('name'))
#This is not working as expected because its raising an error ie. " Process is still
#in use"
default_storage.delete(file_name)
return Response(result)
If you have any better approach to match fingerprints or images that will also be appreciated. Thanks in advance. The current code works fine and matches the fingerprint as expected but its storing the incoming fingerprint image and I don't want that.
For me this problem was solved using file_path instead of file_name:
file_name = default_storage.save(file.name, file)
file_path = os.path.join(settings.MEDIA_ROOT, file_name)
. . .
default_storage.delete(file_path)
file = default_storage.open(file_name)
...
default_storage.delete(file_name)
That is why that file is open when you try to delete it.
This has nothing to do with OpenCV. The point of a minimal reproducible example is to make you figure out where the issue is.
It looks like more an opencv issue than django-rest-framework's one.
Please refer to this issue:
https://github.com/opencv/opencv/issues/12342
Looks like your solution might be just to upgrade your opencv package version.

Resize and Create Thumbnail on Image Upload Django 2 using Pillow

I am trying to resize an image on upload and also create a thumbnail out of the same resized image. I previously was able to resize the image without issues, however, when I tried adding in the thumbnail feature I was unable to get it working. I have looked at all the other related questions and all of them are either outdated or haven't worked in my case.
models.py
class Project(models.Model):
cover_image=models.ImageField(max_length=150, upload_to='project-covers/', default='Default.png', null=True)
thumbnail=models.ImageField(max_length=150, upload_to='project-thumbnails/', null=True)
def save(self, *args, **kwargs):
# pdb.set_trace()
if self.cover_image:
fname = self.title + '_cover.'
tname = self.title + '_thumbnail.'
self.resizeUploadedImage(fname)
self.createThumbnail(tname)
super(Project, self).save(*args, **
def resizeUploadedImage(self, fname):
'''Resize the image being uploaded.'''
try:
im = Image.open(self.cover_image)
if im.width > IMAGE_SIZE[0] or im.heght > IMAGE_SIZE[1]:
im.resize(IMAGE_SIZE, Image.ANTIALIAS)
image_io = BytesIO()
im.save(image_io, im.format)
# pdb.set_trace()
fname = fname + im.format
self.cover_image.save(fname, ContentFile(image_io.read(), False))
im.close()
image_io.close()
except IOError as e:
print("Could not resize image for", self.image)
print(e)
def createThumbnail(self, fname):
'''Create thumbnail of the image.'''
try:
if self.thumbnail is None:
im = Image.open(self.cover_image)
im.thumbnail(THUMB_SIZE)
image_io = BytesIO()
im.save(image_io, im.format)
fname = fname + im.format
self.thumbnail.save(fname, ContentFile(image_io.getvalue(), False))
im.close()
except IOError as e:
print('Could not create a thumbnail for', self.image)
print(e)
Originally I was using the resizeUploadedImage and createThumbnail methods and was successful with the resizing however the thumbnail was always empty on my admin page and db. Before I had 'editable=False' on the thumbnail as I wanted it to be created automatically behind the scenes. I thought that might be preventing the change so I took it out but it didn't change the results.
Then I tried moving both into the save method (as I had previously run into issues when I moved my resize outside the save) but it still doesn't work properly.
I see on several documents that it's best to put the super() call at the end of the save method, but when I do that I get
UNIQUE constraint failed: projects_project.id
How can I create a thumbnail out of the cover_image and save it to the thumbnail field?
P.S. While running ./manage.py test projects it seems to be actually saving files to my disk which I thought wasn't supposed to happen so I assume its some kind of issue with my Pillow code. I end up with '1_cover.PNG', '1Project0_cover.PNG', '1Project1_cover.PNG', '2_cover.PNG' etc, etc, etc counting up to 9 even though my setuptestdata only has 'Project 1'.
P.P.S. I read somewhere that it's better to use Pillow's thumbnail function for resizing as it'll keep the aspect ratio whereas resize won't. Does anyone have any insight on this?
from PIL import Image
def save(self):
super().save()
img = Image.open(self.cover_image.path)
if img.height > 250 or img.width > 250:
output_size = (250, 250)
img.thumbnail(output_size)
img.save(self.cover_image.path)
try this way with the size you wanted instead of 250

Django modify image before it's handled by stdimage

I'm using django-stdimage for creating variations of the image.
class Photo(models.Model):
photo = StdImageField(upload_to='photos', verbose_name=_("photo"),
variations={'large': (600, 600), 'thumbnail': (100, 100)}
StdImageField does it's own operations on the image, subclassing ImageField and having attr_class = StdImageFieldFile
StdImageFieldFile does the actual save operation
class StdImageFieldFile(ImageFieldFile):
"""
Like ImageFieldFile but handles variations.
"""
def save(self, name, content, save=True):
super(StdImageFieldFile, self).save(name, content, save)
render_variations = self.field.render_variations
if callable(render_variations):
render_variations = render_variations(
file_name=self.name,
variations=self.field.variations,
storage=self.storage,
)
if not isinstance(render_variations, bool):
msg = (
'"render_variations" callable expects a boolean return value,'
' but got %s'
) % type(render_variations)
raise TypeError(msg)
if render_variations:
self.render_variations()
However, I want to do some manipulation of the image before StdImageFieldFile does it (rotating).
So I created my custom field, to catch the image before it's passed to stdimage
class Rotate(ImageFieldFile):
def save(self, name, content, save=True):
save = False
return super(Rotate, self).save(name, content, save)
class StdImageFieldFileRotateMixin(Rotate, StdImageFieldFile):
pass
class StdImageFieldRotate(StdImageField):
attr_class = StdImageFieldFileRotateMixin
I have the image in the content property of the Rotate class and I can manipulate the image using PIL, but after it's done, I don't know how to assign this image back to the content property. It seems that it's generated on the lower level. Is there a method to generate this content property and then MRO will handle the rest (i.e. pass it to StdImageFieldFile and it will do the rest)?
You can pass a callable to the argument render_variations of the StdImageField class and pre-process the image there.
For example assume you want to save disk space and resize the original image to a smaller version, taking only the smaller image together with variations created by django-stdimage. You can write a function like this:
from io import BytesIO
from PIL import Image
from django.core.files.base import ContentFile
from stdimage.utils import render_variations
def preprocess(file_name, variations, storage):
with storage.open(file_name) as f:
with Image.open(f) as image:
file_format = image.format
# resize to a maximum of 1000x1000 keeping aspect ratio
image.thumbnail((1000, 1000), resample=Image.ANTIALIAS)
with BytesIO() as file_buffer:
image.save(file_buffer, file_format)
f = ContentFile(file_buffer.getvalue())
# delete the original big image
storage.delete(file_name)
# save the resized version with the same filename and format
storage.save(file_name, f)
# render stdimage variations
render_variations(file_name, variations, replace=True, storage=storage)
return False # prevent default rendering
You can then pass the function to the render_variations argument of the StdImageField class, like this:
class Photo(models.Model):
photo = StdImageField(upload_to='photos', verbose_name=_("photo"),
variations={'large': (600, 600), 'thumbnail': (100, 100)},
render_variations=preprocess)
This technique is inspired from the Async image processing example in the README of the django-stdimage project on Github.
Also the way in which the image is handled in the preprocess function is inspired from the stdimage.models.StdImageFieldFile.render_variation method and uses Django Storage objects to deal with different storage types for image files (think Amazon S3).

uploading multiple models to sketchfab

The follwowing is a code written in blender python
import bpy
import os
import subprocess
import time
bpy.data.materials["Material"].use_face_texture = True
customer_id = "john33"
image1 = "orangeskin.jpg" # define image 1 from folder
imagepath1 = "C:\\Users\\mrryan\\Desktop\\models\\materialsjpeg\\"
image2 = "elec.jpg"
imagepath2 = "C:\\Users\\mrryan\\Desktop\\models\\materialsjpeg\\"
#Step 1 go to Edit Mode
bpy.ops.object.editmode_toggle()
#step 2 insert image in the Image Editor
bpy.context.area.type = 'IMAGE_EDITOR'
bpy.ops.uv.smart_project()
bpy.ops.image.open(filepath = image1, directory= imagepath1 ,
files=[{"name":image1, "name":image1}])
#Step 3 back to Object Mode TRY AND CHANGE TITLE TO `CUSTOMER_ID AND FILE`
bpy.ops.object.editmode_toggle()
bpy.data.window_managers["WinMan"].sketchfab.title = "orangetube"
#Step 4 save new.blend
filepath = os.path.join(r"C:\Users\mrryan\Desktop", customer_id + "orangytube.blend")
bpy.ops.wm.save_as_mainfile(filepath = filepath)
toggleedit = bpy.ops.object.editmode_toggle()
#here is the problem!!!!
subprocess.call(["bpy.ops.export.sketchfab()"], shell = True)
#new texture
#Step 1 go to Edit Mode
#step 2 insert image in the Image Editor
bpy.context.area.type = 'IMAGE_EDITOR'
bpy.ops.image.open(filepath= image2, directory= imagepath2,
files= [{"name":image2,"name":image2}])
bpy.ops.uv.smart_project()
#Step 3 back to Object Mode
bpy.ops.object.editmode_toggle()
bpy.data.window_managers["WinMan"].sketchfab.title = "elec-tube"
#Step 4 save new.blend
filepath = os.path.join(r"C:\Users\mrryan\Desktop", customer_id + "elec-tube.blend")
bpy.ops.wm.save_as_mainfile(filepath = filepath)
bpy.ops.export.sketchfab()
The idea is, eventually to programmatically change the active object( which is combined but not joined with another object) to have different materials and change the shape, etc. Each individual model combination would be uploaded to sketchfab separately and saved as a blender file.
At this stage my problem is that, while uploading, the script does not wait until the first upload is finished.This generates an error that says to please wait for the current upload to finish. and as a result only one model is uploaded.
subprocess.Popen
has been tried but i could not get it to work.
Perhaps the return values were wrong?
How can you get the script to upload the first model, wait until finished, then continue to make the adjustments and upload the second?
(I work at Sketchfab, but I don't have much experience in Blender scripting).
The issue is that the Sketchfab Blender Exporter uses a thread for the upload, meaning the subprocess will return, with a thread still running in the background.
I would experiment in launching bpy.ops.export.sketchfab() using a new Thread and join(), instead of just subprocess.
The following resources might help:
python multithreading wait till all threads finished
https://docs.python.org/2/library/multiprocessing.html
http://www.blender.org/documentation/blender_python_api_2_62_2/info_gotcha.html#strange-errors-using-threading-module
Let us know how it goes!

File responses in Python Pyramid 'cancelling' each other out?

Here are three Pyramid view_callables which setup a page with data and two image files. Trouble is, only one of the images (file responses) is returned. It seems I can only return one of the images at a time. If I take one of the file response vc's away, the other images is returned. However, if I have both file respone vc's there, only the second image is returned. Is there some object in the first vc I'm overwriting with the second vc?
Is there a better way to return both images (files), even within the first # title vc? As it is now, even if it worked, I have to retrieve the same document from the database 3 times for the one template. Any advice or clues would be greatly appreciated.
# title
#view_config(
route_name='title',
renderer='templates/titles/title.jinja2')
def title(request):
title = Title().find_one({'_id':ObjectId(request.matchdict['_id'])})
result = dict(
user = request.user,
title = title)
return result
# view title image
#view_config(route_name="view_title_image")
def jpg(request):
fd = Title().find_one({'_id':ObjectId(request.matchdict['title_id'])}).TitleImage
response = Response(content_type='application/jpg')
response.app_iter = fd.File
print fd
return response
# view trailer thumbnail
#view_config(route_name="view_trailer_thumbnail")
def jpg(request):
fd = Title().find_one({'_id':ObjectId(request.matchdict['title_id'])}).TrailerThumbnail
response = Response(content_type='application/jpg')
response.app_iter = fd.File
print fd
return response
Here are the route configs from __init__:
# title
config.add_route('title', '/title/{_id}')
# view title image
config.add_route('view_title_image', '/view/title_image/{title_id}')
# view title image
config.add_route('view_trailer_thumbnail', '/view/trailer_thumbnail/{title_id}')
This is how its used in the Jinja2 template:
<img src="/view/title_image/{{ title._id }}">
<img src="/view/trailer_thumbnail/{{ title._id }}">
I think your problem is that both views have the function named jpg.
Although it's not a great idea to overwrite functions like that, I would have thought that this would be no problem at all for the view_config decorator. The only thing I can think of is that rather recording a reference to the function, view_config works out what the dotted path would be and records that.
Anyway, give the view functions different names and you should be fine.

Categories

Resources