I'm attempting to upload a profile picture to a django model which should always be named pic.jpg. Old pictures are deleted using django_cleanup.
This works every other time. I upload an image and it's saved as pic.jpg, then upload a different one and it's saved as pic_{randomchars}.jpg (i.e pic_wCU5xwv.jpg).
def rename_pic(instance, filename):
return os.path.join("api/media/me/", filename)
pic = models.ImageField(upload_to=rename_pic)
def save(self, *args, **kwargs):
try:
# Opening the uploaded image
img = Image.open(self.pic)
output = BytesIO()
img = img.convert('RGB')
# after modifications, save it to the output
img.save(output, format='JPEG')
output.seek(0)
# Set field to modified picture
self.pic = InMemoryUploadedFile(output, 'ImageField', "pic.jpg",
'image/jpeg', sys.getsizeof(output), None)
except Exception as e:
print(e)
print(self.pic.name) # Always prints pic.jpg
super(MyData, self).save() # Error happens in this line
print(self.pic.name) # Prints api/media/me/pic.jpg and api/media/me/pic_{randomchars}.jpg alternating
The error happens somewhere in the super(MyData, self).save() line, as the file has the correct name before it's called.
Related
So I am trying to generate a QR code on creation of the Device Model and save it to the device model, for some reason even though I can generate the image, I can't save it to the model.
I've tried saving with a filebuffer instead, that gave me an error, I just don't know what else to try.
The error is The 'qrcode' attribute has no file associated with it.
def Device(models.Model):
qrcode = models.ImageField(blank=True, upload_to="qrcode")
def save(self, *args, **kwargs)
self.generate_qrcode()
super(Device, self).save(*args, **kwargs)
def generate_qrcode(self):
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=6,
border=0,
)
qr.add_data("http://localhost/ims/device/" + str(self.id))
qr.make(fit=True)
img = qr.make_image()
filename = str(self.id) + '.jpg'
img.save(filename)
i create a function that create a thumbnail when uploading an image. I can upload the image and create the thumbnail out of it. But the result that i got is the thumbnail was created more than one until and i got the error that i've mention aboved.
def save(self, *args, **kwargs):
"""
Make and save the thumbnail for the photo here.
"""
super(AerialFoto, self).save(*args, **kwargs)
if not self.make_thumbnail():
raise Exception('Could not create thumbnail - is the file type valid?')
def make_thumbnail(self):
"""
Create and save the thumbnail for the photo (simple resize with PIL).
"""
THUMB_SIZE = (100,100)
fh = storage.open(self.image.name, 'rb')
try:
image = Image.open(fh)
except:
return False
image.thumbnail(THUMB_SIZE, Image.ANTIALIAS)
fh.close()
# Path to save to, name, and extension
thumb_name, thumb_extension = os.path.splitext(self.image.name)
thumb_extension = thumb_extension.lower()
thumb_filename = thumb_name + 'thumbs' + thumb_extension
if thumb_extension in ['.jpg', '.jpeg']:
FTYPE = 'JPEG'
elif thumb_extension == '.gif':
FTYPE = 'GIF'
elif thumb_extension == '.png':
FTYPE = 'PNG'
else:
return False # Unrecognized file type
# Save thumbnail to in-memory file as StringIO
temp_thumb = StringIO()
image.save(temp_thumb, FTYPE)
temp_thumb.seek(0)
# Load a ContentFile into the thumbnail field so it gets saved
self.thumbnail.save(thumb_filename, ContentFile(temp_thumb.read()), save=True)
temp_thumb.close()
Traceback
http://dpaste.com/1ZG838R
save() calls make_thumbnail(), which calls self.thumbnail.save(...), which ends up calling save() again, and around and around it goes.
You have to break the loop somewhere. My suggestion: make_thumbnail() shouldn't save anything, it should just create the thumbnail and store it on say self._thumbnail_data= temp_thumb.read();.
Then in the save() function, only call self.make_thumbnail() if self._thumbnail_data isn't already set. Once you know self._thumbnail_data exists then you can do self.thumbnail.save(thumb_filename, self._thumbnail_data, save=True).
This happens because save calls make_thumbnail and make_thumbnail calls save again, you need to break the loop when the make_thumbnail has already saved the thumbnail.
Check in the save args if a thumbnail content is provided and if not call make_thumbnail
In my views.py file, I am trying to add 1 to a BigIntegerField named visited_counter.
views.py :
def view_page(request,id_page):
page = get_object_or_404(Page,title=id_page)
page.visited_counter= page.visited_counter +1
page.save()
return render(request,'app/page.html',locals())
models.py :
class Page(models.Model):
title = models.CharField(max_length=30)
visited_counter= models.BigIntegerField()
landscape= models.BooleanField()
thumbnail = models.ImageField(storage=OverwriteStorage(),upload_to=thumbnail_path)
backgroundpicture =models.ImageField(storage=OverwriteStorage(),upload_to=background_path)
def save(self, *args, **kwargs):
if self.backgroundpicture.width >= self.backgroundpicture.height:
self.landscape=True
else:
self.landscape=False
if self.backgroundpicture:
from PIL import Image
import glob, os
from cStringIO import StringIO
from django.core.files.uploadedfile import SimpleUploadedFile
image = Image.open(self.backgroundpicture) ####LINE ERROR####
try:
# thumbnail
THUMBNAIL_SIZE = (160, 160) # dimensions
# Convert to RGB if necessary
if image.mode not in ('L', 'RGB'): image = image.convert('RGB')
# create a thumbnail + use antialiasing for a smoother thumbnail
image.thumbnail(THUMBNAIL_SIZE, Image.ANTIALIAS)
# fetch image into memory
temp_handle = StringIO()
image.save(temp_handle, 'JPEG')
temp_handle.seek(0)
# save it
file_name, file_ext = os.path.splitext(self.backgroundpicture.name.rpartition('/')[-1])
suf = SimpleUploadedFile(file_name + file_ext, temp_handle.read(), content_type='JPEG')
self.thumbnail.save(file_name + '.jpg', suf, save=False)
except ImportError:
pass
super(Page, self).save(*args, **kwargs)
When I create a 'Page object', I have no problem.... The save function is doing her job very well, but when I want to access to the object via the view_page function. I get a I/O operation on closed file error on that line: image = Image.open(self.backgroundpicture).
I didn't find any other Q/A related to this case, so I am stuck...
The trick is to add an if condition in your save method and check if it is necessary to read the whole code in the save function.
For this you add a function named, has_changed
def has_changed(instance, field, manager='objects'):
"""Returns true if a field has changed in a model
May be used in a model.save() method.
"""
if not instance.pk:
return True
manager = getattr(instance.__class__, manager)
old = getattr(manager.get(pk=instance.pk), field)
return not getattr(instance, field) == old
And you use it in the model definition like this:
if has_changed(self, 'backgroundpicture'):
if self.backgroundpicture.width >= self.backgroundpicture.height:
self.landscape=True
...
I'm trying to figure out the best way to take a user uploaded image, resize it, and store the original image as well as the resized image on Amazon S3.
I'm running Django 1.5, using PIL to resize the image, and using Boto to handle uploading the image file to S3. Right now I've got it to work by uploading the original image to S3, using PIL to open the image using the S3 path and resize it, and then saving the resized version to S3, however this doesn't seem to be the most efficient way to do this.
I'm wondering if there's a way to resize the image before uploading to S3 using the user-uploaded image itself (been having trouble getting PIL to open the image file itself), and whether this would be faster than the way I've set things up now. I can't seem to find an answer to this, either in the PIL documentation or anywhere else. I should mention that I don't want to just use a third party app to handle this, as part of my goal is to learn and understand fundamentally what is going on.
Is there a more efficient way to do this than what I've currently set up? A general explanation of what is happening at each step and why it makes the most sense to set things up that way would be ideal.
I should also mention that it seems to take much longer to upload the image to S3 than when I was just storing the image on my server. Is there a normal lag when uploading to S3 or is there potentially something in how things are set up that could be slowing down the S3 uploads?
I have an architecture consisting of a Django + Tastypie in Heroku and the image wharehouse in S3. What I do when a user uploads a photo from the frontend (written in JS), is resize the photo to a certain size (600 x 600 max size) always mantaining the aspect ratio. I'll paste the code to do this (it works).
views.py:
class UploadView(FormView):
form_class = OriginalForm
def form_valid(self, form):
original = form.save()
if original.image_width > 280 and original.image_height > 281:
if original.image_width > 600 or original.image_height > 600:
original.resize((600, 600))
if not original.image:
return self.success(self.request, form, None, errors = 'Error while uploading the image')
original.save()
up = UserProfile.objects.get(user = request.user.pk)
#Save the images to s3
s3 = S3Custom()
new_image = s3.upload_file(original.image.path, 'avatar')
#Save the s3 image path, as string, in the user profile
up.avatar = new_image
up.save
else:
return self.success(self.request, form, None, errors = 'The image is too small')
return self.success(self.request, form, original)
Here what I do is checking if the image is larger than 280 x 281 (the crop square, in the frontend, has that size), and also check if one of the sides of the image is larger than 600px. If that's the case, I call the (custom) method resize, of my Original class...
models.py:
class Original(models.Model):
def upload_image(self, filename):
return u'avatar/{name}.{ext}'.format(
name = uuid.uuid4().hex,
ext = os.path.splitext(filename)[1].strip('.')
)
def __unicode__(self):
return unicode(self.image)
owner = models.ForeignKey('people.UserProfile')
image = models.ImageField(upload_to = upload_image, width_field = 'image_width', height_field = 'image_height')
image_width = models.PositiveIntegerField(editable = False, default = 0)
image_height = models.PositiveIntegerField(editable = False, default = 0)
def resize(self, size):
if self.image is None or self.image_width is None or self.image_height is None:
print 'Cannot resize None things'
else:
IMG_TYPE = os.path.splitext(self.image.name)[1].strip('.')
if IMG_TYPE == 'jpeg':
PIL_TYPE = 'jpeg'
FILE_EXTENSION = 'jpeg'
elif IMG_TYPE == 'jpg':
PIL_TYPE = 'jpeg'
FILE_EXTENSION = 'jpeg'
elif IMG_TYPE == 'png':
PIL_TYPE = 'png'
FILE_EXTENSION = 'png'
elif IMG_TYPE == 'gif':
PIL_TYPE = 'gif'
FILE_EXTENSION = 'gif'
else:
print 'Not a valid format'
self.image = None
return
#Open the image from the ImageField and save the path
original_path = self.image.path
fp = open(self.image.path, 'rb')
im = Image.open(StringIO(fp.read()))
#Resize the image
im.thumbnail(size, Image.ANTIALIAS)
#Save the image
temp_handle = StringIO()
im.save(temp_handle, PIL_TYPE)
temp_handle.seek(0)
#Save image to a SimpleUploadedFile which can be saved into ImageField
suf = SimpleUploadedFile(os.path.split(self.image.name)[-1], temp_handle.read(), content_type=IMG_TYPE)
#Save SimpleUploadedFile into image field
self.image.save('%s.%s' % (os.path.splitext(suf.name)[0],FILE_EXTENSION), suf, save=False)
#Delete the original image
fp.close()
os.remove(original_path)
#Save other fields
self.image_width = im.size[0]
self.image_height = im.size[1]
return
The last thing you need is a "library" containing custom s3 methods:
class S3Custom(object):
conn = S3Connection(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY)
b = Bucket(conn, settings.AWS_STORAGE_BUCKET_NAME)
k = Key(b)
def upload_file(self, ruta, prefix):
try:
self.k.key = '%s/%s' % (prefix, os.path.split(ruta)[-1])
self.k.set_contents_from_filename(ruta)
self.k.make_public()
except Exception, e:
print e
return '%s%s' % (settings.S3_URL, self.k.key)
You should have AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_STORAGE_BUCKET_NAME, S3_URL in your settings file.
I'm using django and jquery for image uploading. First we upload an image using the uploader, the image get stored in the tmpbg imagefield. Then if we click save button, the tmpbg will be moved to the background imagefield. Here what I need is I want to delete the orphaned image file in the background imagefield path.
Here is the code
Models.py:
class BackgroundModel(models.Model):
user = models.OneToOneField(User)
background = models.ImageField(upload_to='backgrounds', null=True, blank=True)
tmpbg = models.ImageField(upload_to='backgrounds', null=True, blank=True)
class BackgroundModelForm(ModelForm):
class Meta:
model = BackgroundModel
exclude = ('user', 'background')
Views.py (The code for deleting the image file in the path):
try:
bg = BackgroundModel.objects.get(user=request.user)
except BackgroundModel.DoesNotExist:
pass
else:
if bg.background != '':
image_path = os.path.join(settings.MEDIA_ROOT, str(bg.background))
try:
os.unlink(image_path)
bg.BackgroundModel.delete()
except:
pass
bg.background = bg.tmpbg
return HttpResponse("")
But the orphaned imagefile is not deleted. What mistakes have I done?
UPDATE:
There is a problem in this line
bg.background=bg.tmpbg
When I checked in the shell mode. It shows none.
>>> g.background
<ImageFieldFile: None>
But there is an image file in tmpbg
>>> g.background
<ImageFieldFile: backgrounds/ijkl.jpg>
So, How can I copy from one imagefield to another field?
Between the image_path code is Correct!.
Try with os.normpath, this should do the trick :
image_path = os.path.normpath(
os.path.join(settings.MEDIA_ROOT, str(bg.background))
)
UPDATE :
try:
bg = BackgroundModel.objects.get(user=request.user)
except BackgroundModel.DoesNotExist:
pass
else:
if bg.background != '':
image_path = os.path.join(settings.MEDIA_ROOT, str(bg.background))
try:
os.unlink(image_path)
except:
pass
bg.background = bg.tmpbg
bg.save()
return HttpResponse("")
I would override the delete method of the model and add file delete there.
I would do something like:
class BackgroundModel(models.Model):
...
def delete(self, using=None):
os.unlink(self.tmpbg.path)
super(BackgroundModel,self).delete()
I didn't check the code, but something like this should work. Tell me if it's not. :)
Try this:
try:
bg = BackgroundModel.objects.get(user=request.user)
except BackgroundModel.DoesNotExist:
pass
else:
if bg.background:
file_to_delete = bg.background.path
bg.background = bg.tmpbg
bg.save()
try:
os.unlink(file_to_delete)
except:
pass
return HttpResponse()
Also, assign ImageFileField to ImageFileField just copy the file path, thus if you do
g.background = g.tmpbg
g.save()
g.background and g.tmpbg will point to the same file. Once the file gets deleted through g.background, g.tmpbg will be unaccessible also. If you don't want this behavior, tell Django to save to a new file:
from django.core.files.base import File
g.background = File(g.tmpbg)
g.save()