Django: agregue una imagen en un ImageField desde la URL de la imagen

85

por favor disculpe mi feo inglés ;-)

Imagina este modelo muy simple:

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

Me gustaría crear una foto a partir de una URL de imagen (es decir, no a mano en el sitio de administración de django).

Creo que necesito hacer algo como esto:

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)

Espero haberte explicado bien el problema, si no me lo dices.

Gracias :)

Editar:

Esto puede funcionar, pero no sé cómo convertir contenta un archivo django:

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)

fuente

Respuestas:

96

Acabo de crear http://www.djangosnippets.org/snippets/1890/ para este mismo problema. El código es similar a la respuesta de pithyless anterior, excepto que usa urllib2.urlopen porque urllib.urlretrieve no realiza ningún manejo de errores de forma predeterminada, por lo que es fácil obtener el contenido de una página 404/500 en lugar de lo que necesita. Puede crear una función de devolución de llamada y una subclase URLOpener personalizada, pero me resultó más fácil crear mi propio archivo temporal como este:

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))
Chris Adams
fuente
3
¿Qué está haciendo la última línea? ¿de qué viene el imobjeto?
sacerdotec
1
@priestc: imfue un poco conciso; en ese ejemplo, imera la instancia del modelo y fileera el nombre poco imaginativo de un FileField / ImageField en esa instancia. Los documentos de API reales aquí son lo que importa: esa técnica debería funcionar en cualquier lugar donde tenga un objeto de archivo Django vinculado a un objeto: docs.djangoproject.com/en/1.5/ref/files/file/…
Chris Adams
26
No es necesario un archivo temporal. Usando en requestslugar de urllib2usted puede hacer: image_content = ContentFile(requests.get(url_image).content)y luego obj.my_image.save("foo.jpg", image_content).
Stan
Stan: las solicitudes simplifican eso, pero IIRC que una sola línea sería un problema a menos que primero llame a raise_for_status () para evitar confusión con errores o respuestas incompletas
Chris Adams
Probablemente sería una buena idea modernizar esto, ya que fue escrito originalmente en la era Django 1.1 / 1.2. Dicho esto, creo que ContentFile todavía tiene el problema de que cargará todo el archivo en la memoria, por lo que una buena optimización sería usar iter_content con un tamaño de fragmento razonable.
Chris Adams
32

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)

despiadado
fuente
Hola, gracias por ayudarme;). El problema es (cito el documento): "Tenga en cuenta que el argumento de contenido debe ser una instancia de Archivo o de una subclase de Archivo". Entonces, ¿tiene alguna solución para crear una instancia de archivo con su contenido?
Verifique la nueva edición; esto ahora debería ser un ejemplo de trabajo (aunque no lo he probado)
piedad
¿Qué pasa con mi modelo, donde también tengo otros campos? Como url, etc., etc. Si id, haga model.image.save (...). ¿Cómo guardo los otros campos? No pueden ser nulos EDITAR: ¿sería algo como esto? >>> car.photo.save ('myphoto.jpg', contents, save = False) >>> car.save ()
Harry
self.url ?? ... ¿qué es self.url aquí ??
DeadDjangoDjoker
@DeadDjangoDjoker No tengo idea, tendrías que preguntarle a la persona que editó mi respuesta. Esta respuesta tiene 5 años en este momento; He vuelto a la solución "funcional" anterior para la posteridad, pero, honestamente, estás mejor con la respuesta de Chris Adam.
piedad
13

Combinando lo que Chris Adams y Stan dijeron y actualizando las cosas para que funcionen en Python 3, si instalas Requests puedes hacer algo como esto:

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)

Documentos más relevantes en la documentación de ContentFile de Django y el ejemplo de descarga de archivos de solicitudes .

metakermit
fuente
5

ImageFieldes solo una cadena, una ruta relativa a su MEDIA_ROOTconfiguración. Simplemente guarde el archivo (es posible que desee usar PIL para verificar que sea una imagen) y complete el campo con su nombre de archivo.

Por lo tanto, se diferencia de su código en que necesita guardar la salida de su urllib.urlopenarchivo a (dentro de su ubicación de medios), calcular la ruta y guardarla en su modelo.

Oli
fuente
5

Lo hago de esta manera en Python 3, que debería funcionar con adaptaciones simples en Python 2. Esto se basa en mi conocimiento de que los archivos que estoy recuperando son pequeños. Si el tuyo no lo es, probablemente recomiendo escribir la respuesta en un archivo en lugar de almacenarla en la memoria.

Se necesita BytesIO porque Django llama a seek () en el objeto de archivo y las respuestas de urlopen no admiten la búsqueda. En su lugar, podría pasar el objeto bytes devuelto por read () al ContentFile de Django.

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))
jbg
fuente
El archivo (io) devuelve <Archivo: Ninguno>
Susaj S Nair
@SusajSNair Eso es solo porque el archivo no tiene un nombre y el __repr__método para Fileescribe el nombre. Si lo prefiere, puede establecer el nameatributo en el Fileobjeto después de crearlo File(io), pero en mi experiencia no importa (aparte de hacer que se vea mejor si lo imprime). mmmv.
jbg
3

Recientemente utilicé el siguiente enfoque dentro de Python 3 y Django 3, tal vez esto también pueda ser interesante para otros. Es similar a la solución de Chris Adams pero para mí ya no funcionó.

# -*- coding: utf-8 -*-
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.attribute_a = True
new_image.attribute_b = 'False'
new_image.file = SimpleUploadedFile(basename, open(tmpfile, "rb").read())
new_image.save()
contmp
fuente
Esta es la única solución que funcionó para mí (Python 3 + Django 2). El único comentario algo trivial es que el nombre base puede no tener la extensión correcta en todos los casos, dependiendo de la URL.
physicsAI
1

Acabo de descubrir que no es necesario generar un archivo temporal:

Transmita contenido de URL directamente desde django a minio

Tengo que almacenar mis archivos en minio y tener contenedores Docker de django sin mucho espacio en el disco y necesito descargar archivos de video grandes, así que esto fue realmente útil para mí.

efes
fuente
-5

esta es la forma correcta y funcional

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()
Ekin Ertaç
fuente
14
Esa no puede ser la manera correcta, usted elude todo el mecanismo de almacenamiento de archivos de FielField y no respeta la API de almacenamiento.
Andre Bossard