Django: добавить изображение в ImageField из URL-адреса изображения

85

пожалуйста, извините за мой уродливый английский ;-)

Представьте себе эту очень простую модель:

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

Я хотел бы создать фотографию из URL-адреса изображения (т.е. не вручную на сайте администратора django).

Думаю, мне нужно сделать что-то вроде этого:

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)

Надеюсь, что я хорошо объяснил проблему, если не подскажите.

Спасибо :)

Редактировать :

Это может сработать, но я не знаю, как преобразовать contentв файл 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)

источник

Ответы:

96

Я только что создал http://www.djangosnippets.org/snippets/1890/ для этой же проблемы. Код аналогичен приведенному выше ответу pithyless, за исключением того, что он использует urllib2.urlopen, потому что urllib.urlretrieve по умолчанию не выполняет никакой обработки ошибок, поэтому легко получить содержимое страницы 404/500 вместо того, что вам нужно. Вы можете создать функцию обратного вызова и собственный подкласс URLOpener, но мне было проще просто создать свой собственный временный файл следующим образом:

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
что делает последняя строчка? от чего imисходит объект?
priestc 03
1
@priestc: imбыло немного кратко - в этом примере это imбыл экземпляр модели, а fileимя FileField / ImageField в этом экземпляре было невообразимым. Фактические документы API здесь имеют значение - этот метод должен работать везде, где у вас есть объект Django File, привязанный к объекту: docs.djangoproject.com/en/1.5/ref/files/file/…
Крис Адамс,
26
Временный файл не нужен. Использование requestsвместо urllib2вы можете: image_content = ContentFile(requests.get(url_image).content)а затем obj.my_image.save("foo.jpg", image_content).
Stan
Стэн: запросы действительно упрощают это, но IIRC, этот однострочный код будет проблемой, если вы сначала не вызовете raise_for_status (), чтобы избежать путаницы с ошибками или неполными ответами
Крис Адамс
Вероятно, было бы неплохо модернизировать его, поскольку он изначально был написан в эпоху Django 1.1 / 1.2. Тем не менее, я считаю, что у ContentFile все еще есть проблема, заключающаяся в том, что он загружает весь файл в память, поэтому для хорошей оптимизации было бы использование iter_content с разумным размером блока.
Крис Адамс,
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)

бессмысленный
источник
Привет, спасибо, что помогли мне;). Проблема в том (цитирую документ): «Обратите внимание, что аргумент содержимого должен быть экземпляром File или подклассом File». Итак, у вас есть какое-нибудь решение для создания экземпляра File с вашим контентом?
Отметьте новое редактирование; теперь это должен быть рабочий пример (хотя я его не тестировал)
бессмысленно
Что насчет моей модели, где у меня есть и другие поля. Например, URL-адрес и т. Д. И т. Д. Если id делает model.image.save (...). как мне сохранить другие поля? Они не могут быть нулевыми EDIT: что-то вроде этого? >>> car.photo.save ('myphoto.jpg', contents, save = False) >>> car.save ()
Гарри
self.url ?? ... что здесь self.url ??
DeadDjangoDjoker
@DeadDjangoDjoker Понятия не имею, вам нужно спросить человека, который редактировал мой ответ. Этому ответу на данный момент 5 лет; Я вернулся к предыдущему «рабочему» решению для потомков, но, честно говоря, вам лучше с ответом Криса Адама.
pithyless
13

Объединив то, что сказали Крис Адамс и Стэн, и обновив вещи для работы на Python 3, если вы установите 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)

Более подходящие документы в документации Django ContentFile и в примере загрузки файла Requests .

метакермит
источник
5

ImageFieldэто просто строка, путь относительно вашей MEDIA_ROOTнастройки. Просто сохраните файл (вы можете использовать PIL, чтобы проверить, что это изображение) и заполните поле его именем.

Таким образом, он отличается от вашего кода тем, что вам нужно сохранить вывод вашего urllib.urlopenфайла (внутри вашего местоположения мультимедиа), определить путь, сохранить его в своей модели.

Оли
источник
5

Я делаю это на Python 3, который должен работать с простой адаптацией на Python 2. Это основано на моих знаниях о том, что файлы, которые я получаю, небольшие. Если у вас нет, я бы, вероятно, рекомендовал записать ответ в файл вместо буферизации в памяти.

BytesIO необходим, потому что Django вызывает функцию seek () для объекта файла, а ответы urlopen не поддерживают поиск. Вместо этого вы можете передать объект байтов, возвращаемый read (), в 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
источник
File (io) возвращает <File: None>
Susaj S Nair
@SusajSNair Просто потому, что у файла нет имени, а __repr__метод для Fileзаписи имени. Если хотите, вы можете установить nameатрибут для Fileобъекта после его создания File(io), но, по моему опыту, это не имеет значения (кроме того, чтобы он выглядел лучше, если вы его распечатаете). ymmv.
jbg 06
3

В последнее время я использую следующий подход в Python 3 и Django 3, возможно, это может быть интересно и другим. Это похоже на решение Криса Адамса, но для меня оно больше не работает.

# -*- 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
источник
Это единственное решение, которое сработало для меня (Python 3 + Django 2). Единственный тривиальный комментарий заключается в том, что базовое имя может не иметь правильного расширения в каждом случае, в зависимости от URL-адреса.
PhysicsAI
1

Только что обнаружил, что вам не нужно создавать временный файл:

Потоковая передача содержимого url прямо из django в minio

Мне нужно хранить свои файлы в minio и иметь контейнеры докеров django без много места на диске и мне нужно загружать большие видеофайлы, так что это было действительно полезно для меня.

эфес
источник
-5

это правильный и рабочий способ

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
Это не может быть правильным путем, вы обходите весь механизм хранения файлов FielField и не обращаете внимания на api хранилища.
Андре Боссар