Django: dodaj obraz w ImageField z adresu URL obrazu

85

przepraszam za mój brzydki angielski ;-)

Wyobraź sobie ten bardzo prosty model:

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

Chciałbym stworzyć zdjęcie z adresu URL obrazu (tj. Nie ręcznie na stronie administratora django).

Myślę, że muszę zrobić coś takiego:

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)

Mam nadzieję, że dobrze wyjaśniłem problem, jeśli nie powiedz mi.

Dziękuję Ci :)

Edytować :

To może działać, ale nie wiem, jak przekonwertować contentna plik 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)

źródło

Odpowiedzi:

96

Właśnie utworzyłem http://www.djangosnippets.org/snippets/1890/ dla tego samego problemu. Kod jest podobny do powyższej odpowiedzi pithyless, z tą różnicą, że używa urllib2.urlopen, ponieważ urllib.urlretrieve domyślnie nie obsługuje żadnej obsługi błędów, więc łatwo jest uzyskać zawartość strony 404/500 zamiast tego, czego potrzebujesz. Możesz utworzyć funkcję zwrotną i niestandardową podklasę URLOpener, ale okazało się, że łatwiej jest po prostu utworzyć własny plik tymczasowy w następujący sposób:

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
źródło
3
co robi ostatnia linijka? co jest imprzedmiotem pochodzących z?
ksiądz
1
@priestc: imbył nieco zwięzły - w tym przykładzie imbył instancją modelu i filebyła niewyobrażalną nazwą FileField / ImageField w tej instancji. Rzeczywista dokumentacja API tutaj ma znaczenie - ta technika powinna działać wszędzie tam, gdzie masz obiekt Django File powiązany z obiektem: docs.djangoproject.com/en/1.5/ref/files/file/ ...
Chris Adams,
26
Plik tymczasowy nie jest potrzebny. Używając requestszamiast tego urllib2możesz zrobić: image_content = ContentFile(requests.get(url_image).content)i wtedy obj.my_image.save("foo.jpg", image_content).
Stan
Stan: prośby to upraszczają, ale IIRC ten jednolinijkowy byłby problemem, chyba że najpierw wywołałeś raise_for_status (), aby uniknąć nieporozumień z błędami lub niekompletnymi odpowiedziami
Chris Adams
Prawdopodobnie dobrym pomysłem byłoby unowocześnienie tego, ponieważ pierwotnie zostało napisane w ery Django 1.1 / 1.2. To powiedziawszy, uważam, że ContentFile nadal ma problem polegający na tym, że załaduje cały plik do pamięci, więc dobra optymalizacja polegałaby na użyciu iter_content z rozsądnym rozmiarem fragmentu.
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)

bezkręgowy
źródło
Cześć, dziękuję za pomoc;). Problem jest następujący (cytuję dokument): „Zauważ, że argument content musi być instancją File lub podklasy File”. Czy masz więc jakieś rozwiązanie umożliwiające utworzenie instancji pliku z zawartością?
Sprawdź nową edycję; powinien to być teraz działający przykład (chociaż go nie testowałem)
bezkręgowy
A co z moim modelem, gdzie mam również inne pola. Na przykład url itp. Jeśli id ​​to model.image.save (...). jak zapisać inne pola? Nie mogą być zerowe EDYCJA: czy to będzie coś takiego? >>> car.photo.save ('myphoto.jpg', content, save = False) >>> car.save ()
Harry
self.url ?? ... co to jest self.url?
DeadDjangoDjoker
@DeadDjangoDjoker Nie mam pojęcia, musiałbyś zapytać osobę, która redagowała moją odpowiedź. W tym momencie ta odpowiedź ma 5 lat; Powróciłem do poprzedniego „działającego” rozwiązania dla potomnych, ale szczerze mówiąc, lepiej będzie, jeśli odpowie ci Chris Adam.
bezkręgowy
13

Łącząc to, co powiedzieli Chris Adams i Stan i aktualizując rzeczy do pracy w Pythonie 3, jeśli zainstalujesz Requests , możesz zrobić coś takiego:

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)

Więcej odpowiednich dokumentów w dokumentacji Django ContentFile i przykładzie pobierania pliku żądań .

metakermit
źródło
5

ImageFieldjest po prostu ciągiem znaków, ścieżką względem twojego MEDIA_ROOTustawienia. Po prostu zapisz plik (możesz użyć PIL, aby sprawdzić, czy jest to obraz) i wypełnij pole nazwą pliku.

Więc różni się od twojego kodu tym, że musisz zapisać wyjście swojego urllib.urlopenpliku do pliku (wewnątrz lokalizacji nośnika), opracować ścieżkę, zapisać ją w swoim modelu.

Oli
źródło
5

Robię to w ten sposób w Pythonie 3, który powinien działać z prostymi adaptacjami w Pythonie 2. Jest to oparte na mojej wiedzy, że pobrane pliki są małe. Jeśli nie, prawdopodobnie zaleciłbym zapisanie odpowiedzi do pliku zamiast buforowania w pamięci.

BytesIO jest potrzebne, ponieważ Django wywołuje funkcję seek () na obiekcie pliku, a odpowiedzi urlopen nie obsługują wyszukiwania. Zamiast tego można przekazać obiekt bytes zwrócony przez read () do ContentFile 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
źródło
File (io) zwraca <File: None>
Susaj S Nair
@SusajSNair To tylko dlatego, że plik nie ma nazwy, a __repr__metoda Filezapisuje tę nazwę. Jeśli wolisz, możesz ustawić nameatrybut na Fileobiekcie po utworzeniu go File(io), ale z mojego doświadczenia nie ma to znaczenia (poza tym, że ładniej wygląda, jeśli go wydrukujesz). ymmv.
jbg
3

Ostatnio używam następującego podejścia w Pythonie 3 i Django 3, może to może być również interesujące dla innych. Jest podobny do rozwiązania Chrisa Adamsa, ale dla mnie już nie działał.

# -*- 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
źródło
To jedyne rozwiązanie, które działało u mnie (Python 3 + Django 2). Jedynie dość trywialnym komentarzem jest to, że basename może nie mieć poprawnego rozszerzenia w każdym przypadku, w zależności od adresu URL.
fizykaAI
1

Właśnie odkryłem, że nie musisz generować pliku tymczasowego:

Przesyłaj zawartość URL bezpośrednio z django do minio

Muszę przechowywać moje pliki w minio i mieć kontenery docker django bez dużej ilości miejsca na dysku i konieczności pobierania dużych plików wideo, więc to było dla mnie bardzo pomocne.

efes
źródło
-5

to jest właściwy i działający sposób

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ç
źródło
14
To nie może być właściwy sposób, omijasz cały mechanizm przechowywania plików FielField i nie zwracasz uwagi na interfejs API magazynu.
Andre Bossard,