Django: add image in an ImageField from image url
Asked Answered
P

10

98

please excuse me for my ugly english ;-)

Imagine this very simple model :

class Photo(models.Model):
    image = models.ImageField('Label', upload_to='path/')

I would like to create a Photo from an image URL (i.e., not by hand in the django admin site).

I think that I need to do something like this :

from myapp.models import Photo
import urllib

img_url = 'http://www.site.com/image.jpg'
img = urllib.urlopen(img_url)
# Here I need to retrieve the image (as the same way that if I put it in an input from admin site)
photo = Photo.objects.create(image=image)

I hope that I've well explained the problem, if not tell me.

Thank you :)

Edit :

This may work but I don't know how to convert content to a django File :

from urlparse import urlparse
import urllib2
from django.core.files import File

photo = Photo()
img_url = 'http://i.ytimg.com/vi/GPpN5YUNDeI/default.jpg'
name = urlparse(img_url).path.split('/')[-1]
content = urllib2.urlopen(img_url).read()

# problem: content must be an instance of File
photo.image.save(name, content, save=True)
Pragmatics answered 8/9, 2009 at 10:29 Comment(0)
B
101

I just created http://www.djangosnippets.org/snippets/1890/ for this same problem. The code is similar to pithyless' answer above except it uses urllib2.urlopen because urllib.urlretrieve doesn't perform any error handling by default so it's easy to get the contents of a 404/500 page instead of what you needed. You can create callback function & custom URLOpener subclass but I found it easier just to create my own temp file like this:

from django.core.files import File
from django.core.files.temp import NamedTemporaryFile

img_temp = NamedTemporaryFile(delete=True)
img_temp.write(urllib2.urlopen(url).read())
img_temp.flush()

im.file.save(img_filename, File(img_temp))
Blankly answered 26/1, 2010 at 18:59 Comment(8)
what is the last line doing? what is the im object coming from?Lathrop
@priestc: im was a bit terse - in that example, im was the model instance and file was the unimaginative name of a FileField/ImageField on that instance. The actual API docs here are what matter – that technique should work anywhere you have a Django File object bound to an object: docs.djangoproject.com/en/1.5/ref/files/file/…Blankly
A temporary file is unnecessary. Using requests instead of urllib2 you can do: image_content = ContentFile(requests.get(url_image).content) and then obj.my_image.save("foo.jpg", image_content).Bullyrag
Stan: requests does simplify that but IIRC that one-liner would be a problem unless you called raise_for_status() first to avoid confusion with errors or incomplete responsesBlankly
It probably would be a good idea to modernize this since it was originally written in the Django 1.1/1.2-era. That said, I believe ContentFile still has the problem that it will load the entire file into memory so a nice optimization would be using iter_content with a reasonable chunk size.Blankly
Very clean solution! Thank you very much!Pottle
@Bullyrag when you use response.raw instead of response.content you don't even have to download the file into memory and could stream it directly to your file field / s3 / minio.Siward
I am getting AttributeError: 'NoneType' object has no attribute 'save' when doing article.featured_image.save(name, ContentFile(response.content), save=True). have any idea why ? article is instance .Reclaim
S
34

from myapp.models import Photo
import urllib
from urlparse import urlparse
from django.core.files import File

img_url = 'http://www.site.com/image.jpg'

photo = Photo()    # set any other fields, but don't commit to DB (ie. don't save())
name = urlparse(img_url).path.split('/')[-1]
content = urllib.urlretrieve(img_url)

# See also: http://docs.djangoproject.com/en/dev/ref/files/file/
photo.image.save(name, File(open(content[0])), save=True)

Shook answered 8/9, 2009 at 10:54 Comment(5)
Hi, thank you for helping me ;). The problem is (I quote the doc) : "Note that the content argument must be an instance of File or of a subclass of File." So do you have any solution to create File instance with your content ?Pragmatics
Check the new edit; this should now be a working example (although I have not tested it)Shook
What about my model, where i have other fields aswell. Like url, etc, etc. If id do model.image.save(...). how do I save the other fields? They cant be null EDIT: woudl it be something like this? >>> car.photo.save('myphoto.jpg', contents, save=False) >>> car.save()Rhpositive
self.url??...what is self.url here??Vassily
@Vassily No idea, you'd have to ask the person who edited my answer. This answer is 5 years old at this point; I've reverted to the previous "working" solution for posterity, but honestly you're better off with Chris Adam's answer.Shook
H
25

Combining what Chris Adams and Stan said and updating things to work on Python 3, if you install Requests you can do something like this:

from urllib.parse import urlparse
import requests
from django.core.files.base import ContentFile
from myapp.models import Photo

img_url = 'http://www.example.com/image.jpg'
name = urlparse(img_url).path.split('/')[-1]

photo = Photo() # set any other fields, but don't commit to DB (ie. don't save())

response = requests.get(img_url)
if response.status_code == 200:
    photo.image.save(name, ContentFile(response.content), save=True)

More relevant docs in Django's ContentFile documentation and Requests' file download example.

Hyperform answered 20/2, 2017 at 10:14 Comment(0)
V
7

ImageField is just a string, a path relative to your MEDIA_ROOT setting. Just save the file (you might want to use PIL to check it is an image) and populate the field with its filename.

So it differs from your code in that you need to save the output of your urllib.urlopen to file (inside your media location), work out the path, save that to your model.

Vexillum answered 8/9, 2009 at 10:35 Comment(0)
V
6

I do it this way on Python 3, which should work with simple adaptations on Python 2. This is based on my knowledge that the files I’m retrieving are small. If yours aren’t, I’d probably recommend writing the response out to a file instead of buffering in memory.

BytesIO is needed because Django calls seek() on the file object, and urlopen responses don’t support seeking. You could pass the bytes object returned by read() to Django's ContentFile instead.

from io import BytesIO
from urllib.request import urlopen

from django.core.files import File


# url, filename, model_instance assumed to be provided
response = urlopen(url)
io = BytesIO(response.read())
model_instance.image_field.save(filename, File(io))
Velvavelvet answered 13/10, 2014 at 0:40 Comment(2)
File(io) returns <File: None>Infernal
@SusajSNair That’s just because the file doesn’t have a name, and the __repr__ method for File writes the name. If you prefer, you could set the name attribute on the File object after creating it with File(io), but in my experience it doesn’t matter (apart from making it look nicer if you print it out). ymmv.Velvavelvet
D
5

Recently I use the following approach within python 3 and Django 3, maybe this might be interesting for others aswell. It is similar to Chris Adams solution but for me it did not work anymore.

import urllib.request
from django.core.files.uploadedfile import SimpleUploadedFile
from urllib.parse import urlparse

from demoapp import models


img_url = 'https://upload.wikimedia.org/wikipedia/commons/f/f7/Stack_Overflow_logo.png'
basename = urlparse(img_url).path.split('/')[-1]
tmpfile, _ = urllib.request.urlretrieve(img_url)

new_image = models.ModelWithImageOrFileField()
new_image.title = 'Foo bar'
new_image.file = SimpleUploadedFile(basename, open(tmpfile, "rb").read())
new_image.save()
Dandelion answered 4/4, 2020 at 22:16 Comment(3)
This is the only solution that worked for me (Python 3 + Django 2). Only somewhat trivial comment is that basename may not have correct extension in every case, depending on URL.Prismatoid
isn't that weird that something so frequent as downloading from url is not included directly inside the models.ImageField class ?Muniments
Your approach works very good for me anyway. ThanksMuniments
S
2

Just discovered that you don't have to generate a temporary file:

Stream url content directly from django to minio

I have to store my files in minio and have django docker containers without much disk space and need to download big video files, so this was really helpful to me.

Siward answered 31/1, 2020 at 7:53 Comment(0)
P
1

I used this snippet to set image as avatar of Person from URL.

import os
from apps.core.models import Person
from io import BytesIO
from django.core.files import File
from urllib.request import urlopen

url = 'https://picsum.photos/256/256'

for person in Person.objects.all():     
    response = urlopen(url)
    avatar_content = BytesIO(response.read())
    filename = os.path.basename(url)
    person.avatar.save(filename, File(avatar_content), save=True)
    
Philosopher answered 11/8, 2023 at 14:29 Comment(0)
E
0

Its been almost 11 years since the question and the most reputed answer has been posted. Thanks To @chris-adams for the response. I am Just reposting the same answer along with the updated packages and support.

#! /usr/bin/python3
# lib/utils.py

import urllib3                                          # http Request Package.
from typing import Optional

from django.core.files import File                      # Handle Files in Django
from django.core.files.temp import NamedTemporaryFile   # handling temporary files.


def fetch_image(url: str, instance: models.Model, field: str, name: Optional[str]=None):
    """
    fetch_image Fetches an image URL and adds it to the model field.
    the parameter instance does not need to be a saved instance. 

    :url: str = A valid image URL.
    :instance: django.db.models.Model = Expecting a model with image field or file field. 
    :field: str = image / file field name as string; 
    [name:str] = Preferred file name, such as product slug or something.  

    :return: updated instance as django.db.models.Model, status of updation as bool.
    
    """
    
    conn = urllib3.PoolManager()
    response = conn.request('GET', url)
    if response.status <> 200:
        print("[X] 404! IMAGE NOT FOUND")
        print(f"TraceBack: {url}")
        return instance, False
    
    file_obj = NamedTemporaryFile(delete=True)
    file_obj.write( response.data )
    file_obj.flush()

    img_format = url.split('.')[-1]
    
    if name is None:
        name = url.split('/')[-1]
    
    if not name.endswith(img_format):
        name += f'.{img_format}'
    
    django_file_obj = File(file_obj)
    (getattr(instance, field)).save(name, django_file_obj)
    return instance, True
    

Tested with Django==2.2.12 in Python 3.7.5


if __name__ == '__main__':
    instance = ProductImage()
    url = "https://www.publicdomainpictures.net/pictures/320000/velka/background-image.png"
    instance, saved = fetch_image(url, instance, field='banner_image', name='intented-image-slug')
    status = ["FAILED! ", "SUCCESS! "][saved]
    print(status, instance.banner_image and instance.banner_image.path)
    instance.delete()

Emmott answered 17/8, 2021 at 8:52 Comment(0)
D
-5

this is the right and working way

class Product(models.Model):
    upload_path = 'media/product'
    image = models.ImageField(upload_to=upload_path, null=True, blank=True)
    image_url = models.URLField(null=True, blank=True)

    def save(self, *args, **kwargs):
        if self.image_url:
            import urllib, os
            from urlparse import urlparse
            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(upload_path, filename)
            self.image_url = ''
            super(Product, self).save()
Dodecanese answered 28/10, 2012 at 3:49 Comment(1)
That can't be the right way, you circumvent the whole file storage mechanism of the FielField and pay no respect to the storage api.Pewee

© 2022 - 2024 — McMap. All rights reserved.