Django: Bild aus der Bild-URL in ein ImageField einfügen

84

bitte entschuldige mich für mein hässliches englisch ;-)

Stellen Sie sich dieses sehr einfache Modell vor:

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

Ich möchte ein Foto aus einer Bild-URL erstellen (dh nicht von Hand auf der Django-Administrationsseite).

Ich denke, dass ich so etwas tun muss:

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)

Ich hoffe, dass ich das Problem gut erklärt habe, wenn nicht, sag es mir.

Danke :)

Bearbeiten:

Das mag funktionieren, aber ich weiß nicht, wie ich contentin eine Django-Datei konvertieren soll:

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)

quelle

Antworten:

95

Ich habe gerade http://www.djangosnippets.org/snippets/1890/ für dasselbe Problem erstellt. Der Code ähnelt der obigen Antwort von pithyless, verwendet jedoch urllib2.urlopen, da urllib.urlretrieve standardmäßig keine Fehlerbehandlung durchführt und es daher einfach ist, den Inhalt einer 404/500-Seite anstelle der benötigten zu erhalten. Sie können eine Rückruffunktion und eine benutzerdefinierte URLOpener-Unterklasse erstellen, aber ich fand es einfacher, einfach meine eigene temporäre Datei wie folgt zu erstellen:

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
quelle
3
Was macht die letzte Zeile? Woher kommt das imObjekt?
Priester
1
@priestc: imwar etwas knapp - in diesem Beispiel imwar es die Modellinstanz und fileder einfallslose Name eines FileField / ImageField auf dieser Instanz. Die eigentlichen API-Dokumente hier sind wichtig - diese Technik sollte überall dort funktionieren, wo ein Django- Dateiobjekt
Chris Adams
26
Eine temporäre Datei ist nicht erforderlich. Verwenden requestsanstelle von können urllib2Sie tun:image_content = ContentFile(requests.get(url_image).content) und dann obj.my_image.save("foo.jpg", image_content).
Stan
Stan: Anfragen vereinfachen das, aber IIRC, dass Einzeiler ein Problem wäre, wenn Sie nicht zuerst Raise_for_status () aufrufen, um Verwechslungen mit Fehlern oder unvollständigen Antworten zu vermeiden
Chris Adams
Es wäre wahrscheinlich eine gute Idee, dies zu modernisieren, da es ursprünglich in der Django 1.1 / 1.2-Ära geschrieben wurde. Trotzdem glaube ich, dass ContentFile immer noch das Problem hat, dass die gesamte Datei in den Speicher geladen wird, sodass eine nette Optimierung die Verwendung von iter_content mit einer angemessenen Blockgröße wäre.
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)

kernlos
quelle
Hallo, danke, dass du mir geholfen hast;). Das Problem ist (ich zitiere das Dokument): "Beachten Sie, dass das Inhaltsargument eine Instanz von File oder einer Unterklasse von File sein muss." Haben Sie also eine Lösung, um eine Dateiinstanz mit Ihren Inhalten zu erstellen?
Überprüfen Sie die neue Bearbeitung. Dies sollte nun ein funktionierendes Beispiel sein (obwohl ich es nicht getestet habe)
kernlos
Was ist mit meinem Modell, wo ich auch andere Bereiche habe? Wie URL usw. usw. Wenn id model.image.save (...). Wie speichere ich die anderen Felder? Sie können nicht null sein. BEARBEITEN: Wäre es so etwas? >>> car.photo.save ('myphoto.jpg', Inhalt, speichern = Falsch) >>> car.save ()
Harry
self.url ?? ... was ist self.url hier ??
DeadDjangoDjoker
@DeadDjangoDjoker Keine Ahnung, Sie müssten die Person fragen, die meine Antwort bearbeitet hat. Diese Antwort ist zu diesem Zeitpunkt 5 Jahre alt; Ich bin zur vorherigen "funktionierenden" Lösung für die Nachwelt zurückgekehrt, aber ehrlich gesagt sind Sie mit Chris Adams Antwort besser dran.
Marklos
13

Kombinieren Sie die Aussagen von Chris Adams und Stan und aktualisieren Sie die Einstellungen für Python 3, wenn Sie sie installieren Folgendes tun Requests :

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)

Weitere relevante Dokumente finden Sie in der ContentFile-Dokumentation von Django und im Beispiel zum Herunterladen von Anforderungsdateien .

Metakermit
quelle
5

ImageField ist nur eine Zeichenfolge, ein Pfad relativ zu Ihrem MEDIA_ROOT Einstellung. Speichern Sie einfach die Datei (möglicherweise möchten Sie PIL verwenden, um zu überprüfen, ob es sich um ein Bild handelt) und füllen Sie das Feld mit dem Dateinamen.

Es unterscheidet sich von Ihrem Code darin, dass Sie die Ausgabe Ihrer urllib.urlopento-Datei (innerhalb Ihres Medienspeicherorts) speichern, den Pfad erarbeiten und in Ihrem Modell speichern müssen.

Oli
quelle
4

Ich mache das auf Python 3, das mit einfachen Anpassungen auf Python 2 funktionieren sollte. Dies basiert auf meinem Wissen, dass die Dateien, die ich abrufe, klein sind. Wenn dies bei Ihnen nicht der Fall ist, würde ich wahrscheinlich empfehlen, die Antwort in eine Datei zu schreiben, anstatt sie im Speicher zu puffern.

BytesIO wird benötigt, da Django seek () für das Dateiobjekt aufruft und urlopen-Antworten die Suche nicht unterstützen. Sie können das von read () zurückgegebene Byte-Objekt stattdessen an Djangos ContentFile übergeben.

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
quelle
Datei (io) gibt <Datei: Keine> zurück
Susaj S Nair
@SusajSNair Das liegt nur daran, dass die Datei keinen Namen hat und die __repr__Methode zum FileSchreiben des Namens. Wenn Sie möchten, können Sie das nameAttribut für das FileObjekt festlegen, nachdem Sie es mit erstellt File(io)haben. Meiner Erfahrung nach spielt es jedoch keine Rolle (abgesehen davon, dass es beim Ausdrucken besser aussieht). ymmv.
jbg
3

Kürzlich verwende ich den folgenden Ansatz in Python 3 und Django 3, vielleicht ist dies auch für andere interessant. Es ist ähnlich wie die Chris Adams-Lösung, aber für mich hat es nicht mehr funktioniert.

# -*- 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
quelle
Dies ist die einzige Lösung, die für mich funktioniert hat (Python 3 + Django 2). Ein nur trivialer Kommentar ist, dass der Basisname je nach URL möglicherweise nicht in jedem Fall die richtige Erweiterung hat.
physicsAI
1

Ich habe gerade festgestellt, dass Sie keine temporäre Datei generieren müssen:

Streame URL-Inhalte direkt von Django nach Minio

Ich muss meine Dateien in Minio speichern und Django-Docker-Container ohne viel Speicherplatz haben und große Videodateien herunterladen, daher war dies für mich sehr hilfreich.

Ephes
quelle
-5

Das ist der richtige und funktionierende Weg

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ç
quelle
14
Das kann nicht der richtige Weg sein, Sie umgehen den gesamten Dateispeichermechanismus des FielField und achten nicht auf die Speicher-API.
Andre Bossard