Salvando programaticamente a imagem no Django ImageField

203

Ok, tentei quase tudo e não consigo fazer isso funcionar.

  • Eu tenho um modelo Django com um ImageField nele
  • Eu tenho um código que baixa uma imagem via HTTP (testado e funciona)
  • A imagem é salva diretamente na pasta 'upload_to' (o upload_to é aquele que está definido no ImageField)
  • Tudo o que preciso fazer é associar o caminho do arquivo de imagem já existente ao ImageField

Eu escrevi este código sobre 6 maneiras diferentes.

O problema que eu estou enfrentando é que todo o código que estou escrevendo resulta no seguinte comportamento: (1) o Django cria um segundo arquivo, (2) renomeia o novo arquivo, adicionando um _ ao final do arquivo (3) não transfira nenhum dado, deixando basicamente um arquivo renomeado vazio. O que resta no caminho 'upload_to' são 2 arquivos, um que é a imagem real e outro que é o nome da imagem, mas está vazio, e é claro que o caminho ImageField é definido como o arquivo vazio que o Django tenta criar .

Caso isso não esteja claro, tentarei ilustrar:

## Image generation code runs.... 
/Upload
     generated_image.jpg     4kb

## Attempt to set the ImageField path...
/Upload
     generated_image.jpg     4kb
     generated_image_.jpg    0kb

ImageField.Path = /Upload/generated_image_.jpg

Como posso fazer isso sem o Django tentar re-armazenar o arquivo? O que eu realmente gostaria é algo nesse sentido ...

model.ImageField.path = generated_image_path

... mas é claro que isso não funciona.

E sim, eu já passei por outras perguntas aqui, como esta , bem como o documento do django no arquivo

ATUALIZAÇÃO Após mais testes, ele só faz esse comportamento quando executado no Apache no Windows Server. Durante a execução no 'runserver' no XP, ele não executa esse comportamento.

Estou perplexo.

Aqui está o código que é executado com sucesso no XP ...

f = open(thumb_path, 'r')
model.thumbnail = File(f)
model.save()
T. Stone
fonte
Outra ótima pergunta do Django. Fiz várias tentativas para resolver este problema sem sorte. Os arquivos criados no diretório de upload estão quebrados e têm apenas uma fração de tamanho em comparação com os originais (armazenados em outro local).
westmark 8/10/09
Sua atualização não funciona
AmiNadimi

Respostas:

166

Eu tenho algum código que busca uma imagem na web e a armazena em um modelo. Os bits importantes são:

from django.core.files import File  # you need this somewhere
import urllib


# The following actually resides in a method of my model

result = urllib.urlretrieve(image_url) # image_url is a URL to an image

# self.photo is the ImageField
self.photo.save(
    os.path.basename(self.url),
    File(open(result[0], 'rb'))
    )

self.save()

Isso é um pouco confuso porque foi retirado do meu modelo e um pouco fora de contexto, mas as partes importantes são:

  • A imagem retirada da web não é armazenada na pasta upload_to, é armazenada como um arquivo temporário por urllib.urlretrieve () e posteriormente descartada.
  • O método ImageField.save () usa um nome de arquivo (o bit os.path.basename) e um objeto django.core.files.File.

Deixe-me saber se você tiver dúvidas ou precisar de esclarecimentos.

Edit: por uma questão de clareza, eis o modelo (menos as instruções de importação necessárias):

class CachedImage(models.Model):
    url = models.CharField(max_length=255, unique=True)
    photo = models.ImageField(upload_to=photo_path, blank=True)

    def cache(self):
        """Store image locally if we have a URL"""

        if self.url and not self.photo:
            result = urllib.urlretrieve(self.url)
            self.photo.save(
                    os.path.basename(self.url),
                    File(open(result[0], 'rb'))
                    )
            self.save()
tvon
fonte
2
tvon - Eu tentei algo nesse sentido, mas talvez eu tente outra vez, na verdade, eu tinha um código que parecia muito semelhante a isso. (Mesmo que esteja fora de contexto, posso ver como funciona).
5160 Stone T.
2
Eu sugeriria o uso de análise de url também para evitar que o URL paramatar gunk seja anexado à imagem. import urlparse. os.path.basename(urlparse.urlparse(self.url).path). Obrigado pelo post, foi útil.
dennmat
1
Recebo django.core.exceptions.SuspiciousOperation: tentativa de acesso a '/images/10.jpg' negada.
DataGreed 13/08/12
2
@DataGreed, você deve remover a barra '/' da definição upload_to no modelo. Isso foi abordado aqui .
tsikov 28/08/2012
Estou recebendo um erro como este:prohibited to prevent data loss due to unsaved related object 'stream'.
Dipak 17/06
95

Super fácil se o modelo ainda não foi criado:

Primeiro , copie seu arquivo de imagem para o caminho de upload (assumido = 'path /' no seguinte snippet).

Segundo , use algo como:

class Layout(models.Model):
    image = models.ImageField('img', upload_to='path/')

layout = Layout()
layout.image = "path/image.png"
layout.save()

testado e funcionando no django 1.4, ele também pode funcionar para um modelo existente.

Rabih Kodeih
fonte
10
Esta é a resposta certa, precisa de mais votos !!! Encontrei esta solução aqui também.
Andrew Swihart
Oi. Eu tenho uma pergunta. Estou usando django-storages com o backend do Amazon S3. Isso acionará um novo upload?
Salvatore Iovene
O OP pergunta "sem que o Django tente armazenar novamente o arquivo", e esta é a resposta para isso!
frnhr
2
O Django possui alguma lógica existente para contabilizar nomes de arquivos duplicados no disco. Esse método ignora essa lógica porque o usuário fica checando a duplicação de nome de arquivo.
Chris Conlan
1
@Conlan: anexa um guid ao nome do arquivo.
Rabih Kodeih 23/10
41

Apenas uma pequena observação. A resposta tvon funciona, mas, se você estiver trabalhando no Windows, provavelmente desejará open()o arquivo 'rb'. Como isso:

class CachedImage(models.Model):
    url = models.CharField(max_length=255, unique=True)
    photo = models.ImageField(upload_to=photo_path, blank=True)

    def cache(self):
        """Store image locally if we have a URL"""

        if self.url and not self.photo:
            result = urllib.urlretrieve(self.url)
            self.photo.save(
                    os.path.basename(self.url),
                    File(open(result[0], 'rb'))
                    )
            self.save()

ou seu arquivo será truncado no primeiro 0x1Abyte.

Tiago Almeida
fonte
1
Obrigado, costumo esquecer esses detalhes de baixo nível que as janelas nos enfrentam.
mike_k
fml ... o que acontece quando esse parâmetro é passado em uma máquina linux?
DMac the Destroyer
1
respondeu minha própria pergunta ... desculpe pelo spam. Encontrei alguma documentação para isso aqui . "No Unix, não custa anexar um 'b' ao modo, para que você possa usá-lo independentemente da plataforma para todos os arquivos binários."
DMac the Destroyer
16

Aqui está um método que funciona bem e permite converter o arquivo para um determinado formato também (para evitar o erro "não é possível gravar o modo P como JPEG"):

import urllib2
from django.core.files.base import ContentFile
from PIL import Image
from StringIO import StringIO

def download_image(name, image, url):
    input_file = StringIO(urllib2.urlopen(url).read())
    output_file = StringIO()
    img = Image.open(input_file)
    if img.mode != "RGB":
        img = img.convert("RGB")
    img.save(output_file, "JPEG")
    image.save(name+".jpg", ContentFile(output_file.getvalue()), save=False)

onde image é o django ImageField ou your_model_instance.image, aqui está um exemplo de uso:

p = ProfilePhoto(user=user)
download_image(str(user.id), p.image, image_url)
p.save()

Espero que isto ajude

michalk
fonte
12

Ok, se tudo o que você precisa fazer é associar o caminho do arquivo de imagem já existente ao ImageField, esta solução pode ser útil:

from django.core.files.base import ContentFile

with open('/path/to/already/existing/file') as f:
  data = f.read()

# obj.image is the ImageField
obj.image.save('imgfilename.jpg', ContentFile(data))

Bem, se for sério, o arquivo de imagem já existente não será associado ao ImageField, mas a cópia desse arquivo será criada em upload_to dir como 'imgfilename.jpg' e será associado ao ImageField.

rmnff
fonte
2
Você não deve abri-lo como um arquivo binário?
Mariusz Jamro
Assim como @MariuszJamro disse, deveria ser assim:with open('/path/to/already/existing/file', 'rb') as f:
rahmatns
também não se esqueça de salvar o objeto:obj.save()
rahmatns 18/02
11

O que fiz foi criar meu próprio armazenamento que não salvará o arquivo no disco:

from django.core.files.storage import FileSystemStorage

class CustomStorage(FileSystemStorage):

    def _open(self, name, mode='rb'):
        return File(open(self.path(name), mode))

    def _save(self, name, content):
        # here, you should implement how the file is to be saved
        # like on other machines or something, and return the name of the file.
        # In our case, we just return the name, and disable any kind of save
        return name

    def get_available_name(self, name):
        return name

Em meus modelos, no ImageField, usei o novo armazenamento personalizado:

from custom_storage import CustomStorage

custom_store = CustomStorage()

class Image(models.Model):
    thumb = models.ImageField(storage=custom_store, upload_to='/some/path')
Nicu Surdu
fonte
7

Se você quiser apenas "definir" o nome do arquivo real, sem incorrer na sobrecarga de carregar e salvar novamente o arquivo (!!), ou recorrer ao uso de um campo de caracteres (!!!), convém tentar algo como isto - -

model_instance.myfile = model_instance.myfile.field.attr_class(model_instance, model_instance.myfile.field, 'my-filename.jpg')

Isso iluminará seu model_instance.myfile.url e todos os demais, como se você realmente tivesse carregado o arquivo.

Como @ t-stone diz, o que realmente queremos é ser capaz de definir instance.myfile.path = 'my-filename.jpg', mas o Django atualmente não suporta isso.

s29
fonte
Se model_instance é a instância do modelo que contém o arquivo .. o que a outra "instância" representa?
h3
7

A solução mais simples na minha opinião:

from django.core.files import File

with open('path_to_file', 'r') as f:   # use 'rb' mode for python3
    data = File(f)
    model.image.save('filename', data, True)
Ivan Semochkin
fonte
3

Muitas dessas respostas estavam desatualizadas e passei muitas horas em frustração (sou bastante novo no Django & web dev em geral). No entanto, encontrei esta excelente essência de @iambibhas: https://gist.github.com/iambibhas/5051911

import requests

from django.core.files import File
from django.core.files.temp import NamedTemporaryFile


def save_image_from_url(model, url):
    r = requests.get(url)

    img_temp = NamedTemporaryFile(delete=True)
    img_temp.write(r.content)
    img_temp.flush()

    model.image.save("image.jpg", File(img_temp), save=True)
Zaya
fonte
2

Esta pode não ser a resposta que você está procurando. mas você pode usar charfield para armazenar o caminho do arquivo em vez do ImageFile. Dessa forma, você pode associar programaticamente a imagem carregada ao campo sem recriar o arquivo.

Mohamed
fonte
Sim, fiquei tentado a desistir disso e escrever diretamente no MySQL, ou apenas usar um CharField ().
T. Pedra
1

Podes tentar:

model.ImageField.path = os.path.join('/Upload', generated_image_path)
panda
fonte
1
class tweet_photos(models.Model):
upload_path='absolute path'
image=models.ImageField(upload_to=upload_path)
image_url = models.URLField(null=True, blank=True)
def save(self, *args, **kwargs):
    if self.image_url:
        import urllib, os
        from urlparse import urlparse
        file_save_dir = self.upload_path
        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(file_save_dir, filename)
        self.image_url = ''
    super(tweet_photos, self).save()
sawan gupta
fonte
1
class Pin(models.Model):
    """Pin Class"""
    image_link = models.CharField(max_length=255, null=True, blank=True)
    image = models.ImageField(upload_to='images/', blank=True)
    title = models.CharField(max_length=255, null=True, blank=True)
    source_name = models.CharField(max_length=255, null=True, blank=True)
    source_link = models.CharField(max_length=255, null=True, blank=True)
    description = models.TextField(null=True, blank=True)
    tags = models.ForeignKey(Tag, blank=True, null=True)

    def __unicode__(self):
        """Unicode class."""
        return unicode(self.image_link)

    def save(self, *args, **kwargs):
        """Store image locally if we have a URL"""
        if self.image_link and not self.image:
            result = urllib.urlretrieve(self.image_link)
            self.image.save(os.path.basename(self.image_link), File(open(result[0], 'r')))
            self.save()
            super(Pin, self).save()
Nishant
fonte
1

Trabalhando! Você pode salvar a imagem usando o FileSystemStorage. veja o exemplo abaixo

def upload_pic(request):
if request.method == 'POST' and request.FILES['photo']:
    photo = request.FILES['photo']
    name = request.FILES['photo'].name
    fs = FileSystemStorage()
##### you can update file saving location too by adding line below #####
    fs.base_location = fs.base_location+'/company_coverphotos'
##################
    filename = fs.save(name, photo)
    uploaded_file_url = fs.url(filename)+'/company_coverphotos'
    Profile.objects.filter(user=request.user).update(photo=photo)
Nids Barthwal
fonte
Obrigado Nids, absolutamente trabalhando nesta solução! Você economizou muito tempo :)
Mehmet Burak Ibis
0

Você pode usar a estrutura REST do Django e a biblioteca de solicitações python para salvar programaticamente a imagem no Django ImageField

Aqui está um exemplo:

import requests


def upload_image():
    # PATH TO DJANGO REST API
    url = "http://127.0.0.1:8080/api/gallery/"

    # MODEL FIELDS DATA
    data = {'first_name': "Rajiv", 'last_name': "Sharma"}

    #  UPLOAD FILES THROUGH REST API
    photo = open('/path/to/photo'), 'rb')
    resume = open('/path/to/resume'), 'rb')
    files = {'photo': photo, 'resume': resume}

    request = requests.post(url, data=data, files=files)
    print(request.status_code, request.reason) 
Rajiv Sharma
fonte
0

Com o Django 3, com um modelo como este:

class Item(models.Model):
   name = models.CharField(max_length=255, unique=True)
   photo= models.ImageField(upload_to='image_folder/', blank=True)

se a imagem já foi carregada, podemos fazer diretamente:

Item.objects.filter(...).update(photo='image_folder/sample_photo.png')

ou

my_item = Item.objects.get(id=5)
my_item.photo='image_folder/sample_photo.png'
my_item.save()
Skratt
fonte