Guardar programáticamente la imagen en Django ImageField


203

Ok, he intentado casi todo y no puedo hacer que esto funcione.

  • Tengo un modelo Django con un ImageField
  • Tengo un código que descarga una imagen a través de HTTP (probado y funciona)
  • La imagen se guarda directamente en la carpeta 'upload_to' (el upload_to es el que está configurado en ImageField)
  • Todo lo que necesito hacer es asociar la ruta del archivo de imagen ya existente con ImageField

He escrito este código sobre 6 formas diferentes.

El problema con el que me encuentro es que todo el código que estoy escribiendo da como resultado el siguiente comportamiento: (1) Django creará un segundo archivo, (2) cambiará el nombre del nuevo archivo y agregará un _ al final del archivo nombre, luego (3) no transfiera ninguno de los datos dejando básicamente un archivo vacío renombrado. Lo que queda en la ruta 'upload_to' son 2 archivos, uno que es la imagen real y otro que es el nombre de la imagen, pero está vacío, y por supuesto, la ruta ImageField está configurada en el archivo vacío que Django intenta crear .

En caso de que eso no esté claro, intentaré 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

¿Cómo puedo hacer esto sin que Django intente volver a almacenar el archivo? Lo que realmente me gustaría es algo en este sentido ...

model.ImageField.path = generated_image_path

... pero por supuesto eso no funciona.

Y sí, he revisado las otras preguntas aquí, como esta , así como el documento de django en File

ACTUALIZACIÓN Después de más pruebas, solo realiza este comportamiento cuando se ejecuta bajo Apache en Windows Server. Mientras se ejecuta bajo el 'servidor de ejecución' en XP, no ejecuta este comportamiento.

Estoy perplejo

Aquí está el código que se ejecuta con éxito en XP ...

f = open(thumb_path, 'r')
model.thumbnail = File(f)
model.save()

Otra gran pregunta de Django. He hecho varios intentos para resolver este problema sin suerte. Los archivos creados en el directorio de carga están rotos y solo tienen una fracción de tamaño en comparación con los originales (almacenados en otro lugar).
westmark

su ACTUALIZACIÓN no funciona
AmiNadimi

Respuestas:


167

Tengo un código que extrae una imagen de la web y la almacena en un modelo. Los bits importantes son:

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

Eso es un poco confuso porque está sacado de mi modelo y un poco fuera de contexto, pero las partes importantes son:

  • La imagen extraída de la web no se almacena en la carpeta upload_to, sino que urllib.urlretrieve () la almacena como archivo temporal y luego se descarta.
  • El método ImageField.save () toma un nombre de archivo (el bit os.path.basename) y un objeto django.core.files.File.

Avíseme si tiene preguntas o necesita aclaraciones.

Editar: en aras de la claridad, aquí está el modelo (menos cualquier declaración de importación requerida):

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

2
tvon: había intentado algo en este sentido, pero tal vez lo intentaré de nuevo, de hecho, tenía un código que se parecía mucho a esto. (Incluso si está fuera de contexto, puedo ver cómo funciona).
T. Stone el

2
Sugiero usar también el análisis de URL para evitar que la urna paramatar se adhiera a la imagen. import urlparse. os.path.basename(urlparse.urlparse(self.url).path). Gracias por el post, fue útil.
dennmat

1
Obtengo django.core.exceptions.SuspiciousOperation: acceso denegado a '/images/10.jpg'.
DataGreed

2
@DataGreed debe eliminar la barra diagonal '/' de la definición upload_to en el modelo. Esto fue abordado aquí .
tsikov

Recibo un error como este:prohibited to prevent data loss due to unsaved related object 'stream'.
Dipak

95

Súper fácil si el modelo aún no se ha creado:

Primero , copie su archivo de imagen a la ruta de carga (se supone = 'ruta /' en el siguiente fragmento).

Segundo , usa algo como:

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

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

probado y funcionando en django 1.4, podría funcionar también para un modelo existente.


10
¡Esta es la respuesta correcta, necesita más votos! Encontré esta solución aquí también.
Andrew Swihart

Hola. Tengo una pregunta. Estoy usando django-storages con el back-end de Amazon S3. ¿Esto desencadenará una nueva carga?
Salvatore Iovene

OP pregunta "sin que Django intente volver a almacenar el archivo", ¡y esta es la respuesta!
viernes

2
Django tiene alguna lógica existente para dar cuenta de nombres de archivo duplicados en el disco. Este método modifica esa lógica porque el usuario debe verificar la duplicación de nombre de archivo.
Chris Conlan

1
@Conlan: agregue un guid al nombre del archivo.
Rabih Kodeih el

41

Solo un pequeño comentario. La respuesta de tvon funciona pero, si estás trabajando en Windows, probablemente quieras hacer open()el archivo 'rb'. Me gusta esto:

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

o obtendrá su archivo truncado en el primer 0x1Abyte.


1
Gracias, tiendo a olvidar esos detalles de bajo nivel que nos enfrentan las ventanas.
mike_k

fml ... ¿qué sucede cuando ese parámetro se pasa en una máquina Linux?
DMac the Destroyer

1
respondí mi propia pregunta ... perdón por el spam. Encontré algo de documentación para esto aquí . "En Unix, no está de más agregar una 'b' al modo, por lo que puede usarlo independientemente de la plataforma para todos los archivos binarios".
DMac the Destroyer

16

Aquí hay un método que funciona bien y le permite convertir el archivo a cierto formato también (para evitar el error "no se puede escribir el 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)

donde image es django ImageField o your_model_instance.image aquí hay un ejemplo de uso:

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

Espero que esto ayude


12

Ok, si todo lo que necesita hacer es asociar la ruta del archivo de imagen ya existente con ImageField, entonces esta solución puede 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))

Bueno, si es serio, el archivo de imagen ya existente no se asociará con ImageField, pero la copia de este archivo se creará en upload_to dir como 'imgfilename.jpg' y se asociará con ImageField.


2
¿No deberías abrirlo como un archivo binario?
Mariusz Jamro

Tal como dijo @MariuszJamro, debería ser así:with open('/path/to/already/existing/file', 'rb') as f:
rahmatns

tampoco olvides guardar el objeto:obj.save()
rahmatns

11

Lo que hice fue crear mi propio almacenamiento que simplemente no guardará el archivo en el 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

Luego, en mis modelos, para mi ImageField, he usado el nuevo almacenamiento personalizado:

from custom_storage import CustomStorage

custom_store = CustomStorage()

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

7

Si desea simplemente "establecer" el nombre de archivo real, sin incurrir en la carga de cargar y volver a guardar el archivo (!!), o recurrir a un charfield (!!!), puede intentar algo como esto: -

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

Esto iluminará su model_instance.myfile.url y todo el resto de ellos como si realmente hubiera subido el archivo.

Como dice @ t-stone, lo que realmente queremos es poder establecer instance.myfile.path = 'my-filename.jpg', pero Django actualmente no lo admite.


Si model_instance es la instancia del modelo que contiene el archivo ... ¿qué significa la otra "instancia"?
h3.

7

La solución más simple en mi opinión:

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)

3

Muchas de estas respuestas estaban desactualizadas, y pasé muchas horas frustradas (soy bastante nuevo en Django y el desarrollador web en general). Sin embargo, encontré esta excelente esencia 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)

2

Es posible que esta no sea la respuesta que está buscando. pero puede usar charfield para almacenar la ruta del archivo en lugar de ImageFile. De esa manera, puede asociar mediante programación la imagen cargada al campo sin volver a crear el archivo.


Sí, tuve la tentación de renunciar a esto y escribir directamente a MySQL, o simplemente usar un CharField ().
T. Stone

1

Puedes probar:

model.ImageField.path = os.path.join('/Upload', generated_image_path)

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

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

1

¡Trabajando! Puede guardar la imagen utilizando FileSystemStorage. mira el siguiente ejemplo

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)

¡Gracias Nids, absolutamente trabajando esta solución! Me ahorraste mucho tiempo :)
Mehmet Burak Ibis

0

Puede usar el marco REST de Django y la biblioteca de solicitudes de Python para guardar la imagen mediante programación en Django ImageField

Aquí hay un ejemplo:

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) 

0

Con Django 3, con un modelo como este:

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

Si la imagen ya se ha subido, podemos hacer directamente:

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

o

my_item = Item.objects.get(id=5)
my_item.photo='image_folder/sample_photo.png'
my_item.save()
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.