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()
I am trying to overwrite the save function. What I want is to resize the original photos to a default size (800 * 534) and then to generate a thumbnails for this picture. However, I found the photos were stored twice.
For example, I add a photo named sample.jpg at first time. There are three photos in my directions. One is in ../Media/photos/ and the others are in ../Media/. When I save this picture again, there are four photos. Two are in ../Media/photos/, and the others are in ../Media/.
I am really confused why Django stores the picture twice and why my pictures were stored in ../Media not in ../Media/photos. What I want is to make only two pictures which the 800*534 picture and its thumbnail picture in ../Media/photos.
Here are my codes.
The class Photo:
class Photo(models.Model):
title = models.CharField(max_length=250)
slug = models.SlugField(max_length=250)
summary = models.TextField(blank=True)
created_date = models.DateField(auto_now_add=True)
modified_date = models.DateField(auto_now=True)
image = models.ImageField(upload_to='photo/')
album = models.ForeignKey(Album, on_delete=models.CASCADE)
is_cover_photo = models.BooleanField(default=False)
The save function I wrote in class Photo
def save(self):
if not self.pk:
filename = self.image
if filename:
print(filename)
img = Image.open(filename)
# save the default size photo
default_size = (800, 534)
img.thumbnail(default_size, Image.ANTIALIAS)
img.save(self.get_img_filename(filename))
# save thumbnail photo
cover_size = (342, 229)
img.thumbnail(cover_size, Image.ANTIALIAS)
img.save(self.get_img_thumb_filename(filename))
super(Photo, self).save()
And the other two functions:
def get_img_filename(self, img):
return img.path
def get_img_thumb_filename(self, img):
img_pre, img_ext = os.path.splitext(img.path)
thumb_name = img_pre + '_thumb' + img_ext
return thumb_name
This happens because your program is still calling the super class's save method which implicitly saves all the fields, including "image". Therefore, you have duplicated images in your server.
you can pass a parametter inside the save function like this:
img.save(self.get_img_filename(filename), save=False)
Your problem is that you are PIL.Image is saving the image localy plus in the Model Db you are calling it another save storing it localy like Lukas Herman said.
you need a way to store Image into ImageField possible without using PIL.Image.save(). here is a link I found : link-stackoverflow
Another Posible Solution is to store using Image Class and store the image and save the path location to CharField or TextField
I'm trying to save an image I download with requests and then edit with Pillow to ImageField in a model. But the object is being created without the image.
This is what I have:
settings.py
MEDIA_ROOT = BASE_DIR + "/media/"
MEDIA_URL = MEDIA_ROOT + "/magicpy_imgs/"
models.py
def create_path(instance, filename):
path = "/".join([instance.group, instance.name])
return path
class CMagicPy(models.Model):
image = models.ImageField(upload_to=create_path)
....
# Custom save method
def save(self, *args, **kwargs):
if self.image:
image_in_memory = InMemoryUploadedFile(self.image, "%s" % (self.image.name), "image/jpeg", self.image.len, None)
self.image = image_in_memory
return super(CMagicPy, self).save(*args, **kwargs)
forms.py
class FormNewCard(forms.Form):
imagen = forms.URLField(widget=forms.URLInput(attrs={'class': 'form-control'}))
views.py
def new_card(request):
template = "hisoka/nueva_carta.html"
if request.method == "POST":
form = FormNewCard(request.POST)
if form.is_valid():
url_image = form.cleaned_data['imagen']
group = form.cleaned_data['grupo']
name = form.cleaned_data['nombre']
description = form.cleaned_data['descripcion']
answer = requests.get(url_image)
image = Image.open(StringIO(answer.content))
new_image = image.crop((22, 44, 221, 165))
stringio_obj = StringIO()
try:
new_image.save(stringio_obj, format="JPEG")
image_stringio = stringio_obj.getvalue()
image_file = ContentFile(image_stringio)
new_card = CMagicPy(group=group, description=description, name=name, image=image_file)
new_card.save()
finally:
stringio_obj.close()
return HttpResponse('lets see ...')
It creates the object but with no image. Please help. I've been trying to solve this for hours.
Background
Though InMemoryUploadedFile is primarily intended to be used by MemoryFileUploadHandler, it can be used for other purposes as well. It should be noted that MemoryFileUploadHandler is for handling the situation when a user uploads a file to your server using a webform or a widget. However what you are handling is a situation the user merely provides a link and you download a file on to your web server.
Let us also recall that ImageFile is essentially a reference to a file stored on your filesystem. Only the name of the file is entered in the database and the file's content itself is stored in the storage system. Django allows you to specify different storage systems so that the file can be saved on the cloud if you need to.
Solution
All you need to do is to pass the content of the image that you generate using Pillow with a filename to ImageField. And that content can be sent through a InMemoryUploaded file or a ContentFile. However there isn't a need to use both.
So this is your model.
class CMagicPy(models.Model):
image = models.ImageField(upload_to=create_path)
# over ride of save method not needed here.
This is your view.
try:
# form stuff here
answer = requests.get(url_image)
image = Image.open(StringIO(answer.content))
new_image = image.rotate(90) #image.crop((0, 0, 22, 22))
stringio_obj = StringIO()
new_image.save(stringio_obj, format="JPEG")
image_file = InMemoryUploadedFile(stringio_obj,
None, 'somefile.jpg', 'image/jpeg',
stringio_obj.len, None)
new_card = CMagicPy()
new_card.image.save('bada.jpg',image_file)
new_card.save()
except:
# note that in your original code you were not catching
# an exception. This is probably what made it harder for
# you to figure out what the root cause of the problem was
import traceback
traceback.print_exc()
return HttpResponse('error')
else:
return HttpResponse('done')
Footnotes
Exception handling has been added because things can and will go wrong.
Instead of using JPEG and image/jpeg you should use answers.headers['Content-type'] and choose the appropriate one.
Try this self.image.save(some_file_path, ContentFile(image_stringio)). And it seems to me that you don't need to override save() in model.
This is my models.py:
class College(models.Model):
name = models.CharField(unique=True, max_length=50,
help_text='Name of the college.'
)
slug = models.SlugField(unique=True)
description = models.TextField(blank = True)
image = models.ImageField(upload_to='site-media/media/college_images/',
default = 'site-media/media/college_images/default.jpeg'
)
user = models.ForeignKey(User)
def get_absolute_url(self):
return "/%s/" % self.slug
def create_thumbnail(self):
if not self.image:
return
THUMBNAIL_SIZE = (250,193)
image = Image.open(StringIO(self.image.read()))
thumb = ImageOps.fit(image, THUMBNAIL_SIZE, Image.ANTIALIAS)
temp_handle = StringIO()
thumb.convert('RGB').save(temp_handle, 'jpeg')
temp_handle.seek(0)
suf = SimpleUploadedFile(os.path.split(self.image.name)[-1],
temp_handle.read(), content_type='image/jpeg')
self.image.save('%s_college_.%s'%(os.path.splitext(suf.name)[0],'jpeg'), suf, save=False)
def save(self, *args, **kwargs):
self.slug = slugify(self.name)
self.create_thumbnail()
super(College, self).save(*args, **kwargs)
I have presented the user with a form to edit just the description. When the description 'POST' is made the 'save()' method above is called. The problem with this is that the thumbnail is created over and over again with a bigger name every time. And, also the previous thumbnail is not deleted from the hard disk. Is it possible, that this 'thumbnail' method doesn't get called over and over again with each edit of the 'description'.
You can check whether you are sending image file in you request post or not. For this You need to call your save in view with one argument request like : college.save(request)
def save(self, request=False, *args, **kwargs):
self.slug = slugify(self.name)
if request and request.FILES.get('image',False):
self.create_thumbnail()
super(College, self).save(*args, **kwargs)
OR
you can differentiate your save and edit using
if self.pk is not None
But it can create problem if you edit your image.
So its your choice How you want to go with it.
There are two reasonable paths I see to handle this. Neither are ideal, so I'll be interested to see if anyone has a better option to offer.
One is to save the filename of the most recent image for which you created a thumbnail as a model field. Then in your save method you can check the filename of your image field against it and create a new thumbmail if it has changed. This has the disadvantage of requiring a new model field, but is more universal in its application.
The other is to override the save method of the form class. You can then check the old image filename by looking at self.instance.image and comparing that against self.cleaned_data['image']. This has the disadvantage of only affecting views that use that form class, but doesn't require changing your data model. You can pass a custom form to the admin, if you're using it, or override the admin class's save_model method.
The simple solution is to NOT try to create the thumbnail at this stage, but only when it's needed, ie (pseudocode example):
class Whatever(models.Model):
image = models.ImageField(...)
#property
def thumbnail(self):
thumb = do_i_have_a_thumbnail_yet(self.image)
if not thumb:
thumb = ok_then_make_me_a_thumbnail_and_store_it_please(self.image)
return thumb
Implementing do_i_have_a_thumbnail_yet() and ok_then_make_me_a_thumbnail_and_store_it_please() is left as an exercise to the reader, but there are librairies or django apps providing such services.
Another - and even better imho - solution is to delegate thumbnails handling to a templatetag or template filter. Why ? because
thumbnails are mostly presentation stuff and do not belong to the model
most of the time you'll need different thumbnails sizes from a same image
the front-end developer may want to be free to change the thumbnail's size without having to ask you to change your backend code and write a migration script to recompute all existing thumbnails.
Ok, I've tried about near everything and I cannot get this to work.
I have a Django model with an ImageField on it
I have code that downloads an image via HTTP (tested and works)
The image is saved directly into the 'upload_to' folder (the upload_to being the one that is set on the ImageField)
All I need to do is associate the already existing image file path with the ImageField
I've written this code about 6 different ways.
The problem I'm running into is all of the code that I'm writing results in the following behavior:
(1) Django will make a 2nd file, (2) rename the new file, adding an _ to the end of the file name, then (3) not transfer any of the data over leaving it basically an empty re-named file. What's left in the 'upload_to' path is 2 files, one that is the actual image, and one that is the name of the image,but is empty, and of course the ImageField path is set to the empty file that Django try to create.
In case that was unclear, I'll try to illustrate:
## Image generation code runs....
/Upload
generated_image.jpg 4kb
## Attempt to set the ImageField path...
/Upload
generated_image.jpg 4kb
generated_image_.jpg 0kb
ImageField.Path = /Upload/generated_image_.jpg
How can I do this without having Django try to re-store the file? What I'd really like is something to this effect...
model.ImageField.path = generated_image_path
...but of course that doesn't work.
And yes I've gone through the other questions here like this one as well as the django doc on File
UPDATE
After further testing, it only does this behavior when running under Apache on Windows Server. While running under the 'runserver' on XP it does not execute this behavior.
I am stumped.
Here is the code which runs successfully on XP...
f = open(thumb_path, 'r')
model.thumbnail = File(f)
model.save()
I have some code that fetches an image off the web and stores it in a model. The important bits are:
from django.core.files import File # you need this somewhere
import urllib
# The following actually resides in a method of my model
result = urllib.urlretrieve(image_url) # image_url is a URL to an image
# self.photo is the ImageField
self.photo.save(
os.path.basename(self.url),
File(open(result[0], 'rb'))
)
self.save()
That's a bit confusing because it's pulled out of my model and a bit out of context, but the important parts are:
The image pulled from the web is not stored in the upload_to folder, it is instead stored as a tempfile by urllib.urlretrieve() and later discarded.
The ImageField.save() method takes a filename (the os.path.basename bit) and a django.core.files.File object.
Let me know if you have questions or need clarification.
Edit: for the sake of clarity, here is the model (minus any required import statements):
class CachedImage(models.Model):
url = models.CharField(max_length=255, unique=True)
photo = models.ImageField(upload_to=photo_path, blank=True)
def cache(self):
"""Store image locally if we have a URL"""
if self.url and not self.photo:
result = urllib.urlretrieve(self.url)
self.photo.save(
os.path.basename(self.url),
File(open(result[0], 'rb'))
)
self.save()
Super easy if model hasn't been created yet:
First, copy your image file to the upload path (assumed = 'path/' in following snippet).
Second, use something like:
class Layout(models.Model):
image = models.ImageField('img', upload_to='path/')
layout = Layout()
layout.image = "path/image.png"
layout.save()
tested and working in django 1.4, it might work also for an existing model.
Just a little remark. tvon answer works but, if you're working on windows, you probably want to open() the file with 'rb'. Like this:
class CachedImage(models.Model):
url = models.CharField(max_length=255, unique=True)
photo = models.ImageField(upload_to=photo_path, blank=True)
def cache(self):
"""Store image locally if we have a URL"""
if self.url and not self.photo:
result = urllib.urlretrieve(self.url)
self.photo.save(
os.path.basename(self.url),
File(open(result[0], 'rb'))
)
self.save()
or you'll get your file truncated at the first 0x1A byte.
Ok, If all you need to do is associate the already existing image file path with the ImageField, then this solution may be helpfull:
from django.core.files.base import ContentFile
with open('/path/to/already/existing/file') as f:
data = f.read()
# obj.image is the ImageField
obj.image.save('imgfilename.jpg', ContentFile(data))
Well, if be earnest, the already existing image file will not be associated with the ImageField, but the copy of this file will be created in upload_to dir as 'imgfilename.jpg' and will be associated with the ImageField.
Here is a method that works well and allows you to convert the file to a certain format as well (to avoid "cannot write mode P as JPEG" error):
import urllib2
from django.core.files.base import ContentFile
from PIL import Image
from StringIO import StringIO
def download_image(name, image, url):
input_file = StringIO(urllib2.urlopen(url).read())
output_file = StringIO()
img = Image.open(input_file)
if img.mode != "RGB":
img = img.convert("RGB")
img.save(output_file, "JPEG")
image.save(name+".jpg", ContentFile(output_file.getvalue()), save=False)
where image is the django ImageField or your_model_instance.image
here is a usage example:
p = ProfilePhoto(user=user)
download_image(str(user.id), p.image, image_url)
p.save()
Hope this helps
What I did was to create my own storage that will just not save the file to the disk:
from django.core.files.storage import FileSystemStorage
class CustomStorage(FileSystemStorage):
def _open(self, name, mode='rb'):
return File(open(self.path(name), mode))
def _save(self, name, content):
# here, you should implement how the file is to be saved
# like on other machines or something, and return the name of the file.
# In our case, we just return the name, and disable any kind of save
return name
def get_available_name(self, name):
return name
Then, in my models, for my ImageField, I've used the new custom storage:
from custom_storage import CustomStorage
custom_store = CustomStorage()
class Image(models.Model):
thumb = models.ImageField(storage=custom_store, upload_to='/some/path')
A lot of these answers were outdated, and I spent many hours in frustration (I'm fairly new to Django & web dev in general). However, I found this excellent gist by #iambibhas: https://gist.github.com/iambibhas/5051911
import requests
from django.core.files import File
from django.core.files.temp import NamedTemporaryFile
def save_image_from_url(model, url):
r = requests.get(url)
img_temp = NamedTemporaryFile(delete=True)
img_temp.write(r.content)
img_temp.flush()
model.image.save("image.jpg", File(img_temp), save=True)
Another possible way to do that:
from django.core.files import File
with open('path_to_file', 'r') as f: # use 'rb' mode for python3
data = File(f)
model.image.save('filename', data, True)
If you want to just "set" the actual filename, without incurring the overhead of loading and re-saving the file (!!), or resorting to using a charfield (!!!), you might want to try something like this --
model_instance.myfile = model_instance.myfile.field.attr_class(model_instance, model_instance.myfile.field, 'my-filename.jpg')
This will light up your model_instance.myfile.url and all the rest of them just as if you'd actually uploaded the file.
Like #t-stone says, what we really want, is to be able to set instance.myfile.path = 'my-filename.jpg', but Django doesn't currently support that.
This is might not be the answer you are looking for. but you can use charfield to store the path of the file instead of ImageFile. In that way you can programmatically associate uploaded image to field without recreating the file.
With Django 3,
with a model such as this one:
class Item(models.Model):
name = models.CharField(max_length=255, unique=True)
photo= models.ImageField(upload_to='image_folder/', blank=True)
if the image has already been uploaded, we can directly do :
Item.objects.filter(...).update(photo='image_folder/sample_photo.png')
or
my_item = Item.objects.get(id=5)
my_item.photo='image_folder/sample_photo.png'
my_item.save()
You can try:
model.ImageField.path = os.path.join('/Upload', generated_image_path)
class tweet_photos(models.Model):
upload_path='absolute path'
image=models.ImageField(upload_to=upload_path)
image_url = models.URLField(null=True, blank=True)
def save(self, *args, **kwargs):
if self.image_url:
import urllib, os
from urlparse import urlparse
file_save_dir = self.upload_path
filename = urlparse(self.image_url).path.split('/')[-1]
urllib.urlretrieve(self.image_url, os.path.join(file_save_dir, filename))
self.image = os.path.join(file_save_dir, filename)
self.image_url = ''
super(tweet_photos, self).save()
class Pin(models.Model):
"""Pin Class"""
image_link = models.CharField(max_length=255, null=True, blank=True)
image = models.ImageField(upload_to='images/', blank=True)
title = models.CharField(max_length=255, null=True, blank=True)
source_name = models.CharField(max_length=255, null=True, blank=True)
source_link = models.CharField(max_length=255, null=True, blank=True)
description = models.TextField(null=True, blank=True)
tags = models.ForeignKey(Tag, blank=True, null=True)
def __unicode__(self):
"""Unicode class."""
return unicode(self.image_link)
def save(self, *args, **kwargs):
"""Store image locally if we have a URL"""
if self.image_link and not self.image:
result = urllib.urlretrieve(self.image_link)
self.image.save(os.path.basename(self.image_link), File(open(result[0], 'r')))
self.save()
super(Pin, self).save()
Working!
You can save image by using FileSystemStorage.
check the example below
def upload_pic(request):
if request.method == 'POST' and request.FILES['photo']:
photo = request.FILES['photo']
name = request.FILES['photo'].name
fs = FileSystemStorage()
##### you can update file saving location too by adding line below #####
fs.base_location = fs.base_location+'/company_coverphotos'
##################
filename = fs.save(name, photo)
uploaded_file_url = fs.url(filename)+'/company_coverphotos'
Profile.objects.filter(user=request.user).update(photo=photo)
class DemoImage(models.Model):
title = models.TextField(max_length=255, blank=False)
image = models.ImageField(blank=False, upload_to="images/DemoImages/")
import requests
import urllib.request
from django.core.files import File
url = "https://path/to/logo.jpg"
# Below 3 lines is to fake as browser agent
# as many sites block urllib class suspecting to be bots
opener = urllib.request.build_opener()
opener.addheaders = [("User-agent", "Mozilla/5.0")]
urllib.request.install_opener(opener)
# Issue command to actually download and create temp img file in memory
result = urllib.request.urlretrieve(url)
# DemoImage.objects.create(title="title", image=File(open(result[0], "rb")))
# ^^ This erroneously results in creating the file like
# images/DemoImages/path/to/temp/dir/logo_image_file
# as opposed to
# images/DemoImages/logo_image_file
# Solution to get the file in images/DemoImages/
reopen = open(result[0], "rb") # Returns a BufferedReader object of the temp image
django_file = File(reopen) # Create the file from the BufferedReader object
demoimg = DemoImage()
demoimg.title = "title"
demoimg.image.save("logo.png", django_file, save=True)
This approach also triggers file upload to cloudinary/S3 if so configured
So, if you have a model with an imagefield with an upload_to attribute set, such as:
class Avatar(models.Model):
image_file = models.ImageField(upload_to=user_directory_path_avatar)
then it is reasonably easy to change the image, at least in django 3.15.
In the view, when you process the image, you can obtain the image from:
self.request.FILES['avatar']
which is an instance of type InMemoryUploadedFile, as long as your html form has the enctype set and a field for avatar...
<form method="post" class="avatarform" id="avatarform" action="{% url avatar_update_view' %}" enctype="multipart/form-data">
{% csrf_token %}
<input id="avatarUpload" class="d-none" type="file" name="avatar">
</form>
Then, setting the new image in the view is as easy as the following (where profile is the profile model for the self.request.user)
profile.avatar.image_file.save(self.request.FILES['avatar'].name, self.request.FILES['avatar'])
There is no need to save the profile.avatar, the image_field already saves, and into the correct location because of the 'upload_to' callback function.
Your can use Django REST framework and python Requests library to Programmatically saving image to Django ImageField
Here is a Example:
import requests
def upload_image():
# PATH TO DJANGO REST API
url = "http://127.0.0.1:8080/api/gallery/"
# MODEL FIELDS DATA
data = {'first_name': "Rajiv", 'last_name': "Sharma"}
# UPLOAD FILES THROUGH REST API
photo = open('/path/to/photo', 'rb')
resume = open('/path/to/resume', 'rb')
files = {'photo': photo, 'resume': resume}
request = requests.post(url, data=data, files=files)
print(request.status_code, request.reason)
I save the image with uuid in django 2 python 3 because thats how django do it:
import uuid
from django.core.files import File
import urllib
httpUrl = "https://miimgeurl/image.jpg"
result = urllib.request.urlretrieve(httpUrl)
mymodel.imagefield.save(os.path.basename(str(uuid.uuid4())+".jpg"),File(open(result[0], 'rb')))
mymodel.save()
if you use admin.py you can solve the problem override (doc on django):
def save_model(self, request, obj, form, change):
obj.image_data = bytes(obj.image_name.read())
super().save_model(request, obj, form, change)
with models.py:
image_name = models.ImageField()
image_data = models.BinaryField()