Remove previous image from media folder when ImageFiled entry modified in Django - python

I have a Model with a ImageField.
class Photo(models.Model):
----
image = models.ImageField(verbose_name=_('Image'),upload_to='images/category/%Y/%m/%d/',
max_length=200,null=True,blank=True)
---
I edited this model and changed the image field by uploading a new image.
My question, Is there a way to delete the previous image from its directory(from media folder) when I updates this entry with new image. I am using django 1.4.3 .

You can either use django's signals or simply overwrite the save method on your model. I would write a signal. Something like the following (note that this is untested):
from django.db.models.signals import pre_save
from django.dispatch import receiver
class Photo(models.Model):
image = ...
#receiver(pre_save, sender=Photo)
def delete_old_image(sender, instance, *args, **kwargs):
if instance.pk:
existing_image = Photo.objects.get(pk=instance.pk)
if instance.image and existing_image.image != instance.image:
existing_image.image.delete(False)

Related

how to change image format when uploading image in django?

When a user uploads an image from the Django admin panel, I want to change the image format to '.webp'. I have overridden the save method of the model. Webp file is generated in the media/banner folder but the generated file is not saved in the database. How can I achieve that?
def save(self, *args, **kwargs):
super(Banner, self).save(*args, **kwargs)
im = Image.open(self.image.path).convert('RGB')
name = 'Some File Name with .webp extention'
im.save(name, 'webp')
self.image = im
But After saving the model, instance of the Image class not saved in the database?
My Model Class is :
class Banner(models.Model):
image = models.ImageField(upload_to='banner')
device_size = models.CharField(max_length=20, choices=Banner_Device_Choice)
from django.core.files import ContentFile
If you already have the webp file, read the webp file, put it into the ContentFile() with a buffer (something like io.BytesIO). Then you can proceed to save the ContentFile() object to a model. Do not forget to update the model field, and save the model!
https://docs.djangoproject.com/en/4.1/ref/files/file/
Alternatively
"django-webp-converter is a Django app which straightforwardly converts static images to WebP images, falling back to the original static image for unsupported browsers."
It might have some save capabilities too.
https://django-webp-converter.readthedocs.io/en/latest/
The cause
You are also saving in the wrong order, the correct order to call the super().save() is at the end.
Edited, and tested solution:
from django.core.files import ContentFile
from io import BytesIO
def save(self, *args, **kwargs):
#if not self.pk: #Assuming you don't want to do this literally every time an object is saved.
img_io = BytesIO()
im = Image.open(self.image).convert('RGB')
im.save(img_io, format='WEBP')
name="this_is_my_webp_file.webp"
self.image = ContentFile(img_io.getvalue(), name)
super(Banner, self).save(*args, **kwargs) #Not at start anymore
hello do it like this:
...
from django.db.models.signals import post_save
from django.dispatch import receiver
class Banner(models.Model):
image = models.ImageField(upload_to='banner')
device_size = models.CharField(max_length=20,choices=Banner_Device_Choice)
#receiver(post_save, sender=Banner)
def create_webp(sender, instance, created, **kwargs):
path = instance.image.path
if instance.image.path[-4:] !=webp:
im = Image.open(path).convert('RGB')
extention = instance.image.path.rsplit(".",2)[1]
file_name = path.replace(extention,"webp")
im.save(file_name, 'webp')
instance.image.path = file_name
instance.save()

How to display image in model instance in django admin instead of image path

I am writing a Django web app and I'm having a problem with displaying an image in existing model instance in Django admin and I hope you will be able to help me with it.
I have a model with ImageField, when I upload an image to my model it is properly uploaded but instead of displaying the image in admin page there is path to the image displayed, please see screenshot:
django admin screenshot
Instead of a path I would like to display actual image.
I checked with multiple solutions but I was only able to find an instructions how to display a thumbnail in a page listing all model instances.
Could you advise me?
You need to modify your admin.py
...
from django.utils.safestring import mark_safe
...
class YourModelAdmin(admin.ModelAdmin):
exclude('img') # this is the name of the actual image field in your model
read_only_fields = ['image'] # this is the field you're defining below
def image(self, obj):
return mark_safe('<img src="{url}" width={"width"} height={"height"} />'.format(
url=obj.img.url, # once again, this is the name of the actual image field in your model
width=obj.img.width, # or define custom width
height=obj.img.height, # same as above
))
And that's it. I tested it out and works on my basic setup.

Overwriting Image with same name - Django

Through my project, I have users upload a profile picture. I save the profile picture as userID.jpg. If they upload a new profile picture, I want to overwrite the old profile picture, so I don't waste storage space. By browsing previously asked questions on stackoverflow, I redefined the OverwriteStorage:
class OverwriteStorage(FileSystemStorage):
def get_available_name(self, name, max_length=None):
if self.exists(name):
os.remove(os.path.join(settings.MEDIA_ROOT, name))
return name
When I upload the profile picture, I can see in the directory on my computer that the picture has been overwritten successfully.The image is saved with the path "media/profile/userID.jpg". However, when I display the image on my site, it is still the old picture.
Through the Django site, when I open the path, I see the old picture, and when I try to change it through the admin, I get the following error:
[WinError 32] The process cannot access the file because it is being used by another process: '\media\\profile\\userID.jpg'
I guess I am incorrectly overwriting the file and another instance is still open, and to solve it, I need to properly close the image before overwriting. I tried doing that, but to to no success.
I did something similar but I used signals to update and delete images.
Firstable, I defined the name of the image in the helpers.py
from django.conf import settings
from datetime import datetime
def upload_to_image_post(self, filename):
"""
Stores the image in a specific path regards to date
and changes the name of the image with for the name of the post
"""
ext = filename.split('.')[-1]
current_date = datetime.now()
return '%s/posts/main/{year}/{month}/{day}/%s'.format(
year=current_date.strftime('%Y'), month=current_date.strftime('%m'),
day=current_date.strftime('%d')) % (settings.MEDIA_ROOT, filename)
SO, I called the def in my model, specifically in the image's field
from django.db import models
from django.utils.text import slugify
from .helpers import upload_to_image_post
class Post(models.Model):
"""
Store a simple Post entry.
"""
title = models.CharField('Title', max_length=200, help_text='Title of the post')
body = models.TextField('Body', help_text='Enter the description of the post')
slug = models.SlugField('Slug', max_length=200, db_index=True, unique=True, help_text='Title in format of URL')
image_post = models.ImageField('Image', max_length=80, blank=True, upload_to=upload_to_image_post, help_text='Main image of the post')
class Meta:
verbose_name = 'Post'
verbose_name_plural = 'Posts'
Finally, I defined signals to update or delete the image before the actions (update or delete) happen in the model.
import os
from django.db import models
from django.dispatch import receiver
from django.db.models.signals import pre_delete, pre_save
from .models import Post
#receiver(pre_delete, sender=Post)
def post_delete(sender, instance, **kwargs):
"""
Deleting the specific image of a Post after delete it
"""
if instance.image_post:
if os.path.isfile(instance.image_post.path):
os.remove(instance.image_post.path)
#receiver(pre_save, sender=Post)
def post_update(sender, instance, **kwargs):
"""
Replacing the specific image of a Post after update
"""
if not instance.pk:
return False
if sender.objects.get(pk=instance.pk).image_post:
old_image = sender.objects.get(pk=instance.pk).image_post
new_image = instance.image_post
if not old_image == new_image:
if os.path.isfile(old_image.path):
os.remove(old_image.path)
else:
return False
I hope, this helped you.
It is interesting to receive and process via signals. In some cases, it may be more convenient than OverwriteStorage(FileSystemStorage).
But, os.remove(filename) is not safe/working without local filesystem. I recommend using Django File Storage API.
from django.core.files.storage import default_storage
os.path.isfile(path) # worse
default_storage.exists(path) # better
os.remove(path) # worse
default_storage.delete(path) # better
Have it rename the old one to userID-old.jpg and then save userID.jpg. It will be quick that no one would likely notice it happening.

In which file should I write codes to resize Image file on Django?

I'd like to resize image and save it to Database on Django. I found several web sites mentioned about resize of Django, some write in models.py, but the others write in views.py. In which file should I write codes, views.py or models.py?
from PIL import Image
img = Image.open('original.jpg', 'r')
img.thumbnail((100, 100), Image.ANTIALIAS)
img.save('thumbnail.jpg', 'JPEG', quality=75, optimize=True)
models.py
class Photo(models.Model):
photo = models.ImageField(upload_to="...")
def save(self, *args, **kwargs):
do_somehting()
super(Photo, self).save()
I think the best practice is when you use background jobs such as celery or something else, and call it in model saving mode.
It's better to use Pillow package and sorl and its thumbnail to make any size thumbnail and index it automatically via redis
Consider this gist:
import picke
create_thumbnail_images.apply_async([pickle.dumps(self.image_path), USER_IMAGE_THUMB_SIZES], countdown=5)
You can can above sample code in save action of your model to create thumbnails in background, so you never become blocked for creating these thumbnails.
You could use imagekit for that, eg:
from django.db import models
from imagekit.models import ProcessedImageField
from imagekit.processors import Resize
class SomeModel(models.Model):
...
photo = models.ProcessedImageField(upload_to="...", processors=[Resize(650, 402))
In your models seems better, since the picture will always be resized (in your website's actions and in the admin panel).

How to Register Images using Django's ORM

How do you register an image file in a Django ImageField without using a form, and not copying any files?
I have several thousand JPGs located at /images, and I want to register them in an Image model similar to:
class Image(models.Model):
image = models.ImageField(upload_to='images', max_length=1000)
hash = models.CharField(max_length=1000, unique=True)
However, all the docs I can find on "loading" images into a Django project assume I'm doing so via a form, which also implies the image will be copied to MEDIA_ROOT. I'm not using a form, and I don't want to re-copy the several thousand JPGs, since they're already where they're supposed to be. I just want to create Image records that will store the filename of all the images I currently have. I've written a simple Python script to loop over each image, but I can't find how to properly create the Image record.
I also want to store a hash of the image content, to prevent duplicate records. e.g.
import hashlib
content = open(image_filename).read()
h = hashlib.sha512()
h.update(content)
imgobj.hash = h.hexdigest()
imgobj.save()
Would I override the default model.Model.save() method to do this?
If you have the script to loop over the images in your directory, you're nearly to a solution. Django will only store the path to the image in your Image.image field so basically all you need to do in your loop is:
#pseudo-code
for image_file in image_files:
image, created = Image.objects.get_or_create(hash=the_hash, \
defaults={'image' : 'path/to/image', 'hash' : the_hash)
That's a pretty easy way to build up only the unique records in your database without having to move the files, or use a form. You're either going to harmlessly return the image by the hash if it exists, or you're going to create a new record.
Hope that helps!
After digging through the code, and piecing together a few snippets I found, the following seems to work for me:
models.py
import os, hashlib
from django.db import models
class Image(models.Model):
image = models.ImageField(upload_to=IMAGE_UPLOAD_TO, max_length=1000)
hash = models.CharField(max_length=1000, unique=True)
def save(self, *args, **kwargs):
# Update image hash to ensure uniqueness.
h = hashlib.sha512()
h.update(self.image.read())
self.hash = h.hexdigest()
return models.Model.save(self, *args, **kwargs)
import_images.py
import os
from django.conf import settings
from django.core.files import File
from myapp import models
fn = os.path.join(settings.MEDIA_ROOT, 'images', 'mytestimage.jpg')
img = models.Image()
img.image.save(fn, File(open(fn, 'r')))
img.save()

Categories

Resources