Programmatically saving image to Django ImageField
Asked Answered
K

21

246

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()
Kowatch answered 20/8, 2009 at 19:42 Comment(2)
Another great Django question. I have made several attempts to solve this problem with no luck. The files created in the upload directory are broken and only a fraction in size compared to the originals(stored elsewhere).Hannigan
your UPDATE does not workImmigrant
M
201

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()
Maximo answered 20/8, 2009 at 19:42 Comment(8)
tvon -- I had tried something to this effect, but perhaps I'll give it another go, in fact, I had code that looked very similar to this. (Even if it's out of context I can see how it works).Kowatch
I'd suggest using url parse as well to avoid getting url paramatar gunk attached to the image. import urlparse. os.path.basename(urlparse.urlparse(self.url).path). Thanks for the post, was helpful.Unreserved
I get django.core.exceptions.SuspiciousOperation: Attempted access to '/images/10.jpg' denied.Heartsick
@Heartsick you should remove the forward slash '/' from the upload_to definition in the model. This was addressed here.Reindeer
I am getting error like this : prohibited to prevent data loss due to unsaved related object 'stream'.Endlong
How do you run self.photo.save() on a local server but actually save it remotely on the production server?Haslett
What does os.path.basename(self.url) refer to? The save path?Sharonsharona
I am far, far removed from Django development but I believe this is calling django.core.files.File.save so the os.path.basename(self.url) would set the filename based on the URL (of the file).Maximo
S
130

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.

Scathe answered 16/10, 2012 at 15:11 Comment(11)
This is the right answer, needs more votes!!! Found this solution here as well.Horsepower
Hi. I have a question. I'm using django-storages with the Amazon S3 backend. Will this trigger a new upload?Erfert
OP asks "without having Django try to re-store the file", and this is the answer to that!Dieterich
also in 2017, this should be the accepted answer! works with django 1.4 up to 1.11Nameplate
@SalvatoreIovene I'm doing it this way with django-storages and S3 and it does not trigger a new upload :)Nihil
Django has some existing logic to account for duplicate filenames on disk. This method clobbers that logic because the user is left to checking filename duplication.Horner
@Conlan: append a guid to the file name.Scathe
Works for Django==3.0.4 as well.Cinereous
I've used a similar approach to set the image path, and it works well. However, I also want to upload this image to AWS S3, how do I do it?Personable
@SalvatoreIovene did you find any workaround to trigger an upload to s3 when the image path is set this way? If not, any other way to accomplish the same?Personable
@SusannePeng really ? Wow that's cool then.Queenhood
C
43

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.

Cray answered 24/7, 2010 at 1:37 Comment(4)
Thanks, I tend to forget such low-level details windows face us with.Striper
fml... what happens when that parameter is passed in on a linux machine?Judge
answered my own question... sorry for the spam. found some documentation for this here. "On Unix, it doesn’t hurt to append a 'b' to the mode, so you can use it platform-independently for all binary files."Judge
Even reading a binary file on OSX, this 'rb' works like magic! ThanksBritten
N
19

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.

Nitrile answered 26/9, 2013 at 19:41 Comment(3)
Shoudn't you open it as a binary file?Menarche
Just like @MariuszJamro said, it should be like this: with open('/path/to/already/existing/file', 'rb') as f:Ret
also don't forget to save the object: obj.save()Ret
V
18

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

Veinstone answered 3/7, 2011 at 22:47 Comment(1)
I am getting error, TypeError: save() got an unexpected keyword argument 'save'Johannisberger
W
13

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)

Wyn answered 4/2, 2020 at 1:27 Comment(0)
G
12

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)
Gladine answered 29/8, 2017 at 12:41 Comment(0)
T
11

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')
Trefor answered 19/11, 2010 at 23:8 Comment(0)
O
7

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.

Overstride answered 10/3, 2011 at 6:6 Comment(1)
If model_instance is the instance of the model which contains the file.. what does the other "instance" stand for ??Josephus
C
2

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.

Carton answered 20/8, 2009 at 22:26 Comment(1)
Yeah, I was tempted to give up on this, and either write to MySQL directly, or just use a CharField().Kowatch
P
2

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()
Pentachlorophenol answered 5/6, 2020 at 20:13 Comment(0)
G
1

You can try:

model.ImageField.path = os.path.join('/Upload', generated_image_path)
Glove answered 24/4, 2011 at 17:44 Comment(1)
AttributeError: property 'path' of 'XXX' object has no setterBritton
D
1
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()
Deuteranope answered 13/1, 2013 at 18:22 Comment(0)
G
1
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()
Greatly answered 16/9, 2016 at 6:45 Comment(0)
H
1

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)
Hearttoheart answered 15/8, 2018 at 11:14 Comment(1)
Thank you Nids, absolutely working this solution! You saved my a lot of time :)Shellfish
V
1

Just for those who have struggled with this issue for some time. I spent a few hours to understand what is needed in order to store from code an image into an ImageField in Django. You have to pass a File object to the ImageField as described in the sample below, the file must be opened, and you have to save the model containing your ImageField before closing the file. This sample works fine:

from pathlib import Path
from django.core.files import File

path = Path(image_file_str_path)
with path.open(mode="rb") as f:
   yourmodel.img_field = File(f, name=path.name)
   yourmodel.save()
Varityper answered 20/4, 2023 at 9:7 Comment(0)
B
0
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

Bedtime answered 23/7, 2020 at 21:38 Comment(0)
P
0

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.

Pearliepearline answered 22/2, 2021 at 12:35 Comment(0)
B
0

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()
Bannasch answered 9/6, 2021 at 18:13 Comment(0)
F
0

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()
Feme answered 1/2, 2022 at 19:9 Comment(0)
T
-1

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) 
Tew answered 17/11, 2017 at 7:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.