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/ для цієї самої проблеми. Код подібний до безжальної відповіді вище, за винятком того, що він використовує 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об’єкт?
священик

1
@priestc: imбув дещо лаконічним - у цьому прикладі imбув екземпляром моделі та fileбув уявною назвою FileField / ImageField на цьому екземплярі. Фактичні документи API тут мають значення - ця техніка повинна працювати в будь-якому місці об’єкта файлу Django, прив’язаного до об’єкта: 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 з тим, що один лайнер буде проблемою, якщо спочатку ви не зателефонуєте lift_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." Тож чи є у вас рішення для створення екземпляра файлу зі своїм вмістом?

Перевірте нове редагування; це тепер має бути робочим прикладом (хоча я його не тестував)
безжальний

Що стосується моєї моделі, де я маю й інші сфери. Як URL-адреса тощо, тощо. Якщо id робить model.image.save (...). як зберегти інші поля? Вони не можуть бути нульовими РЕДАКТУВАТИ: це буде щось подібне? >>> car.photo.save ('myphoto.jpg', content, save = False) >>> car.save ()
Гаррі,

self.url ?? ... що тут self.url ??
DeadDjangoDjoker

@DeadDjangoDjoker Не знаю, вам доведеться запитати того, хто редагував мою відповідь. На даний момент цій відповіді 5 років; Я повернувся до попереднього "робочого" рішення для нащадків, але, чесно кажучи, вам краще відповісти Кріс Адам.
безжальний

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 та прикладі завантаження файлу запитів .


5

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

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


5

Я роблю це таким чином на Python 3, який повинен працювати з простими адаптаціями на Python 2. Це базується на моїх знаннях про те, що файли, які я отримую, невеликі. Якщо це не так, я, мабуть, рекомендую записати відповідь у файл, а не буферизувати в пам'яті.

BytesIO потрібен, оскільки Django викликає шукати () для файлового об'єкта, а відповіді 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))

Файл (io) повертає <Файл: Немає>
Susaj S Nair

@SusajSNair Це лише тому, що файл не має імені, а __repr__метод Fileзапису ім’я. Якщо ви віддаєте перевагу, ви можете встановити nameатрибут на Fileоб’єкт після його створення File(io), але, на мій досвід, це не має значення (крім того, щоб зробити його більш приємним, якщо ви його роздрукуєте). ymmv.
jbg

3

Нещодавно я використовую наступний підхід в python 3 і Django 3, можливо, це може бути цікавим і для інших. Це схоже на рішення Кріса Адамса, але для мене воно вже не працювало.

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

Це єдине рішення, яке працювало для мене (Python 3 + Django 2). Тільки дещо тривіальним коментарем є те, що базове ім'я може не мати правильного розширення в кожному випадку, залежно від URL-адреси.
physicsAI

2

Щойно виявив, що вам не потрібно створювати тимчасовий файл:

Передайте вміст URL-адреси безпосередньо з django на minio

Я повинен зберігати свої файли в minio і мати контейнери django docker без багато місця на диску, і мені потрібно завантажувати великі відеофайли, тому це було мені дуже корисно.


-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 зберігання.
Андре Боссард,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.