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()
Related
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.
So, I'm trying to do a little bit of compression of images as well as some other operations. I had a few questions...I have the following save() method for my user class:
class User(AbstractBaseUser, PermissionsMixin):
...
avatar = models.ImageField(storage=SITE_UPLOAD_LOC, null=True, blank=True)
def save(self, *args, **kwargs):
if self.avatar:
img = Img.open(BytesIO(self.avatar.read()))
if img.mode != 'RGB':
img = img.convert('RGB')
new_width = 200
img.thumbnail((new_width, new_width * self.avatar.height / self.avatar.width), Img.ANTIALIAS)
output = BytesIO()
img.save(output, format='JPEG', quality=70)
output.seek(0)
self.avatar= InMemoryUploadedFile(file=output, field_name='ImageField', name="%s.jpg" % self.avatar.name.split('.')[0], content_type='image/jpeg', size=, charset=None)
super(User, self).save(*args, **kwargs)
I had two questions:
Best way for deleting the old avatar file on save, if a previous avatar exists
What do I pass into InMemoryUploadedFile for the size kwarg? Is this the size of the file? in what unit/(s)?
You need to get file size. Try this:
import os
size = os.fstat(output.fileno()).st_size
You can read this for how to get file size:
https://docs.python.org/3.0/library/stat.html
and for deleting old avtar. According to your code it is foreign key hence before saving you can check if avtar is already exists and so yoy can delete it.
Add this lines code after output.seek(0) this line:
if self.avtar:
self.avtar.delete()
What you want is the size in bytes.
You can get the byte size of a BytesIO object like this.
size = len(output.getvalue())
I want to put buttons "Rotate left" and "Rotate right" for images in django.
It seems to be easy, but i've lost some time, tryed some solutions found on stackoverflow and no results yet.
My model has a FileField:
class MyModel(models.Model):
...
file = models.FileField('file', upload_to=path_and_rename, null=True)
...
I'm trying something like this:
def rotateLeft(request,id):
myModel = myModel.objects.get(pk=id)
photo_new = StringIO.StringIO(myModel.file.read())
image = Image.open(photo_new)
image = image.rotate(-90)
image_file = StringIO.StringIO()
image.save(image_file, 'JPEG')
f = open(myModel.file.path, 'wb')
f.write(##what should be here? Can i write the file content this way?##)
f.close()
return render(request, '...',{...})
Obviously, it's not working. I thought that this would be simple, but i don't understand PIL and django file system well yet, i'm new in django.
Sorry for my bad english. I appreciate any help.
from django.core.files.base import ContentFile
def rotateLeft(request,id):
myModel = myModel.objects.get(pk=id)
original_photo = StringIO.StringIO(myModel.file.read())
rotated_photo = StringIO.StringIO()
image = Image.open(original_photo)
image = image.rotate(-90)
image.save(rotated_photo, 'JPEG')
myModel.file.save(image.file.path, ContentFile(rotated_photo.getvalue()))
myModel.save()
return render(request, '...',{...})
P.S. Why do you use FileField instead of ImageField?
UPDATE:
Using python 3, we can do like this:
my_model = MyModel.objects.get(pk=kwargs['id_my_model'])
original_photo = io.BytesIO(my_model.file.read())
rotated_photo = io.BytesIO()
image = Image.open(original_photo)
image = image.rotate(-90, expand=1)
image.save(rotated_photo, 'JPEG')
my_model.file.save(my_model.file.path, ContentFile(rotated_photo.getvalue()))
my_model.save()
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
...