Django: aggiungi un'immagine in un ImageField dall'URL dell'immagine


86

per favore scusami per il mio brutto inglese ;-)

Immagina questo modello molto semplice:

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

Vorrei creare una foto da un URL di immagine (cioè, non a mano nel sito di amministrazione di django).

Penso di dover fare qualcosa del genere:

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)

Spero di aver spiegato bene il problema, se non dimmi.

Grazie :)

Modificare :

Potrebbe funzionare ma non so come convertire contentin un file 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)

Risposte:


96

Ho appena creato http://www.djangosnippets.org/snippets/1890/ per questo stesso problema. Il codice è simile alla risposta di Pithyless sopra tranne che utilizza urllib2.urlopen perché urllib.urlretrieve non esegue alcuna gestione degli errori per impostazione predefinita, quindi è facile ottenere il contenuto di una pagina 404/500 invece di quello che ti serve. Puoi creare una funzione di callback e una sottoclasse URLOpener personalizzata, ma ho trovato più semplice creare il mio file temporaneo in questo modo:

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))

3
cosa fa l'ultima riga? da cosa improviene l' oggetto?
priestc

1
@priestc: imera un po 'conciso: in quell'esempio, imera l'istanza del modello ed fileera il nome privo di fantasia di un FileField / ImageField su quell'istanza. I documenti API effettivi qui sono ciò che conta: quella tecnica dovrebbe funzionare ovunque tu abbia un oggetto File Django associato a un oggetto: docs.djangoproject.com/en/1.5/ref/files/file/…
Chris Adams

27
Non è necessario un file temporaneo. Usando requestsinvece di urllib2te puoi fare: image_content = ContentFile(requests.get(url_image).content)e poi obj.my_image.save("foo.jpg", image_content).
Stan

Stan: le richieste lo semplificano, ma l'IIRC quella battuta sarebbe un problema a meno che non si chiami prima raise_for_status () per evitare confusione con errori o risposte incomplete
Chris Adams

Probabilmente sarebbe una buona idea modernizzarlo poiché è stato originariamente scritto nell'era Django 1.1 / 1.2. Detto questo, credo che ContentFile abbia ancora il problema di caricare l'intero file in memoria, quindi una buona ottimizzazione sarebbe usare iter_content con una dimensione ragionevole.
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)


Ciao, grazie per avermi aiutato;). Il problema è (cito il documento): "Nota che l'argomento del contenuto deve essere un'istanza di File o di una sottoclasse di File." Quindi hai qualche soluzione per creare istanze di file con i tuoi contenuti?

Controlla la nuova modifica; questo dovrebbe ora essere un esempio funzionante (anche se non l'ho testato)
pithyless

E il mio modello, dove ho anche altri campi. Come url, ecc., Ecc. Se id, fai model.image.save (...). come faccio a salvare gli altri campi? Non possono essere nulli EDIT: sarebbe qualcosa del genere? >>> car.photo.save ('myphoto.jpg', contents, save = False) >>> car.save ()
Harry

self.url ?? ... cosa è self.url qui ??
DeadDjangoDjoker

@DeadDjangoDjoker Non ne ho idea, dovresti chiedere alla persona che ha modificato la mia risposta. Questa risposta è vecchia di 5 anni a questo punto; Sono tornato alla precedente soluzione "funzionante" per i posteri, ma onestamente stai meglio con la risposta di Chris Adam.
sostanza

13

Combinando ciò che hanno detto Chris Adams e Stan e aggiornando le cose per funzionare su Python 3, se installi Requests puoi fare qualcosa del genere:

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)

Documenti più rilevanti nella documentazione ContentFile di Django e nell'esempio di download del file Requests .


5

ImageField è solo una stringa, un percorso relativo al tuo MEDIA_ROOT impostazione. Basta salvare il file (potresti voler usare PIL per verificare che sia un'immagine) e popolare il campo con il suo nome di file.

Quindi è diverso dal codice in quanto è necessario salvare l'output del urllib.urlopenfile su (all'interno della posizione del supporto), elaborare il percorso, salvarlo sul modello.


5

Lo faccio in questo modo su Python 3, che dovrebbe funzionare con semplici adattamenti su Python 2. Ciò si basa sulla mia conoscenza che i file che sto recuperando sono piccoli. In caso contrario, probabilmente consiglierei di scrivere la risposta su un file invece di eseguire il buffering in memoria.

BytesIO è necessario perché Django chiama seek () sull'oggetto file e le risposte urlopen non supportano la ricerca. Potresti invece passare l'oggetto byte restituito da read () al ContentFile di 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))

File (io) restituisce <File: Nessuno>
Susaj S Nair

@SusajSNair Questo è solo perché il file non ha un nome e il __repr__metodo per Filescrive il nome. Se preferisci, potresti impostare l' nameattributo Filesull'oggetto dopo averlo creato con File(io), ma nella mia esperienza non importa (a parte renderlo più bello se lo stampi). ymmv.
jbg

3

Recentemente utilizzo il seguente approccio all'interno di python 3 e Django 3, forse questo potrebbe essere interessante anche per altri. È simile alla soluzione di Chris Adams ma per me non ha funzionato più.

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()

Questa è l'unica soluzione che ha funzionato per me (Python 3 + Django 2). Solo un commento un po 'banale è che basename potrebbe non avere l'estensione corretta in ogni caso, a seconda dell'URL.
fisica AI

2

Ho appena scoperto che non è necessario generare un file temporaneo:

Streaming di contenuti URL direttamente da Django a Minio

Devo archiviare i miei file in minio e avere contenitori docker django senza molto spazio su disco e ho bisogno di scaricare file video di grandi dimensioni, quindi questo è stato davvero utile per me.


-5

questo è il modo giusto e funzionante

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()

14
Non può essere il modo giusto, aggiri l'intero meccanismo di archiviazione dei file di FielField e non rispetti l'API di archiviazione.
Andre Bossard
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.