Django: adicionar imagem em um ImageField a partir do url da imagem

86

por favor me desculpe pelo meu inglês feio ;-)

Imagine este modelo muito simples:

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

Eu gostaria de criar uma foto a partir de um URL de imagem (ou seja, não manualmente no site de administração do django).

Acho que preciso fazer algo assim:

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)

Espero ter explicado bem o problema, se não me diga.

Obrigado :)

Editar:

Isso pode funcionar, mas não sei como converter contentpara um arquivo 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)

fonte

Respostas:

96

Acabei de criar http://www.djangosnippets.org/snippets/1890/ para este mesmo problema. O código é semelhante à resposta sem precisão acima, exceto que usa urllib2.urlopen porque urllib.urlretrieve não executa nenhum tratamento de erros por padrão, então é fácil obter o conteúdo de uma página 404/500 em vez do que você precisava. Você pode criar uma função de retorno de chamada e uma subclasse URLOpener personalizada, mas achei mais fácil apenas criar meu próprio arquivo temporário assim:

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
fonte
3
o que a última linha está fazendo? de onde vem o imobjeto?
priestc de
1
@priestc: imfoi um pouco conciso - naquele exemplo, imera a instância do modelo e fileera o nome sem imaginação de um FileField / ImageField naquela instância. A documentação da API atual é o que importa - essa técnica deve funcionar em qualquer lugar onde você tenha um objeto Django File vinculado a um objeto: docs.djangoproject.com/en/1.5/ref/files/file/…
Chris Adams
27
Um arquivo temporário é desnecessário. Usando em requestsvez de urllib2você pode fazer: image_content = ContentFile(requests.get(url_image).content)e então obj.my_image.save("foo.jpg", image_content).
Stan,
Stan: as solicitações simplificam isso, mas IIRC aquele one-liner seria um problema, a menos que você chamasse raise_for_status () primeiro para evitar confusão com erros ou respostas incompletas
Chris Adams
Provavelmente seria uma boa ideia modernizar isso, já que foi originalmente escrito na era Django 1.1 / 1.2. Dito isso, acredito que ContentFile ainda tem o problema de carregar o arquivo inteiro na memória, então uma boa otimização seria usar iter_content com um tamanho de bloco razoável.
Chris Adams de
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)

sem miolo
fonte
Oi, obrigado por me ajudar;). O problema é (cito o documento): "Observe que o argumento de conteúdo deve ser uma instância de Arquivo ou de uma subclasse de Arquivo." Então, você tem alguma solução para criar uma instância de arquivo com seu conteúdo?
Verifique a nova edição; este agora deve ser um exemplo funcional (embora eu não tenha testado)
cúpula,
E o meu modelo, onde também tenho outros campos. Como url, etc, etc. Se id faça model.image.save (...). como faço para salvar os outros campos? Eles não podem ser nulos EDITAR: seria algo assim? >>> car.photo.save ('myphoto.jpg', contents, save = False) >>> car.save ()
Harry
self.url ?? ... o que é self.url aqui ??
DeadDjangoDjoker
@DeadDjangoDjoker Não faço ideia, você teria que perguntar à pessoa que editou minha resposta. Esta resposta tem 5 anos neste ponto; Eu voltei para a solução anterior de "trabalho" para a posteridade, mas honestamente você está melhor com a resposta de Chris Adam.
pithyless
13

Combinando o que Chris Adams e Stan disseram e atualizando as coisas para funcionar no Python 3, se você instalar Requests, poderá fazer algo assim:

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)

Documentos mais relevantes na documentação ContentFile do Django e exemplo de download de arquivo Requests .

metakermit
fonte
5

ImageFieldé apenas uma string, um caminho relativo à sua MEDIA_ROOTconfiguração. Basta salvar o arquivo (você pode usar PIL para verificar se é uma imagem) e preencher o campo com o nome do arquivo.

Portanto, ele difere do seu código porque você precisa salvar a saída do seu urllib.urlopenarquivo em (dentro do local da mídia), descobrir o caminho e salvá-lo no seu modelo.

Oli
fonte
5

Eu faço isso dessa forma no Python 3, que deve funcionar com adaptações simples no Python 2. Isso se baseia no meu conhecimento de que os arquivos que estou recuperando são pequenos. Se o seu não for, provavelmente recomendo gravar a resposta em um arquivo em vez de armazená-la em buffer na memória.

BytesIO é necessário porque o Django chama a busca () no objeto de arquivo e as respostas urlopen não suportam a busca. Você poderia passar o objeto bytes retornado por read () para ContentFile do 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
fonte
O arquivo (io) retorna <Arquivo: Nenhum>
Susaj S Nair
@SusajSNair Isso porque o arquivo não tem nome, e o __repr__método para Filegrava o nome. Se preferir, você pode definir o nameatributo no Fileobjeto depois de criá-lo File(io), mas, na minha experiência, isso não importa (além de torná-lo mais bonito se você imprimi-lo). ymmv.
jbg
3

Recentemente, usei a seguinte abordagem em python 3 e Django 3, talvez isso também seja interessante para outros. É semelhante à solução de Chris Adams, mas para mim não funcionou mais.

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()
contmp
fonte
Esta é a única solução que funcionou para mim (Python 3 + Django 2). Apenas um comentário trivial é que o nome de base pode não ter a extensão correta em todos os casos, dependendo da URL.
físicaAI
2

Acabei de descobrir que você não precisa gerar um arquivo temporário:

Transmita conteúdo de url diretamente do django para o minio

Eu tenho que armazenar meus arquivos em minio e ter contêineres docker django sem muito espaço em disco e preciso baixar arquivos de vídeo grandes, então isso foi realmente útil para mim.

efes
fonte
-5

esta é a maneira certa e funcionando

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ç
fonte
14
Essa não pode ser a maneira certa, você contorna todo o mecanismo de armazenamento de arquivos do FielField e não respeita a API de armazenamento.
Andre Bossard,