Enregistrement par programme d'une image dans Django ImageField


203

Ok, j'ai essayé à peu près tout et je n'arrive pas à faire fonctionner cela.

  • J'ai un modèle Django avec un ImageField dessus
  • J'ai du code qui télécharge une image via HTTP (testé et fonctionne)
  • L'image est enregistrée directement dans le dossier 'upload_to' (le upload_to étant celui qui est défini sur ImageField)
  • Tout ce que je dois faire est d'associer le chemin du fichier image déjà existant à ImageField

J'ai écrit ce code sur 6 façons différentes.

Le problème que je rencontre est tout le code que j'écris entraîne le comportement suivant: (1) Django créera un 2ème fichier, (2) renommera le nouveau fichier, en ajoutant un _ à la fin du fichier nom, puis (3) ne transférez aucune des données plutôt que de le laisser essentiellement un fichier renommé vide. Ce qui reste dans le chemin 'upload_to' est 2 fichiers, un qui est l'image réelle et un qui est le nom de l'image, mais est vide, et bien sûr le chemin ImageField est défini sur le fichier vide que Django essaie de créer .

Au cas où cela ne serait pas clair, je vais essayer d'illustrer:

## 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

Comment puis-je faire cela sans que Django essaie de re-stocker le fichier? Ce que j'aimerais vraiment, c'est quelque chose à cet effet ...

model.ImageField.path = generated_image_path

... mais bien sûr, cela ne fonctionne pas.

Et oui, je suis passé par les autres questions ici comme celle-ci ainsi que le doc django sur File

MISE À JOUR Après des tests supplémentaires, il ne fait ce comportement que lors de l'exécution sous Apache sur Windows Server. Lors de l'exécution sous le «runserver» sous XP, il n'exécute pas ce comportement.

Je suis perplexe.

Voici le code qui fonctionne avec succès sous XP ...

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

Une autre grande question Django. J'ai tenté à plusieurs reprises de résoudre ce problème sans succès. Les fichiers créés dans le répertoire de téléchargement sont cassés et ne représentent qu'une fraction de la taille par rapport aux originaux (stockés ailleurs).
Westmark

votre MISE À JOUR ne fonctionne pas
AmiNadimi

Réponses:


167

J'ai du code qui récupère une image sur le Web et la stocke dans un modèle. Les bits importants sont:

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

C'est un peu déroutant car il est sorti de mon modèle et un peu hors contexte, mais les parties importantes sont:

  • L'image extraite du Web n'est pas stockée dans le dossier upload_to, elle est plutôt stockée en tant que fichier temporaire par urllib.urlretrieve () et ensuite supprimée.
  • La méthode ImageField.save () prend un nom de fichier (le bit os.path.basename) et un objet django.core.files.File.

Faites-moi savoir si vous avez des questions ou besoin d'éclaircissements.

Edit: pour plus de clarté, voici le modèle (moins toutes les déclarations d'importation requises):

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 - J'avais essayé quelque chose à cet effet, mais je vais peut-être essayer à nouveau, en fait, j'avais un code qui ressemblait beaucoup à cela. (Même si c'est hors contexte, je peux voir comment cela fonctionne).
T. Stone

2
Je suggérerais également d'utiliser l'analyse d'url pour éviter d'attacher la saleté de paramatar d'URL à l'image. import urlparse. os.path.basename(urlparse.urlparse(self.url).path). Merci pour le message, a été utile.
dennmat

1
J'obtiens django.core.exceptions.SuspiciousOperation: tentative d'accès à '/images/10.jpg' refusée.
DataGreed

2
@DataGreed vous devez supprimer la barre oblique '/' de la définition upload_to dans le modèle. Cela a été abordé ici .
tsikov

Je reçois une erreur comme celle-ci:prohibited to prevent data loss due to unsaved related object 'stream'.
Dipak

95

Super facile si le modèle n'a pas encore été créé:

Première , copiez votre fichier image dans le chemin de téléchargement (supposé = 'chemin /' dans l'extrait suivant).

Seconde , utilisez quelque chose comme:

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

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

testé et fonctionnant dans django 1.4, il peut également fonctionner pour un modèle existant.


10
Ceci est la bonne réponse, a besoin de plus de votes !!! J'ai également trouvé cette solution ici .
Andrew Swihart

Salut. J'ai une question. J'utilise django-storages avec le backend Amazon S3. Cela déclenchera-t-il un nouveau téléchargement?
Salvatore Iovene

OP demande "sans que Django essaie de re-stocker le fichier", et c'est la réponse à cela!
2014

2
Django a une logique existante pour prendre en compte les noms de fichiers en double sur le disque. Cette méthode encombre cette logique car l'utilisateur est laissé à la vérification de la duplication des noms de fichiers.
Chris Conlan

1
@Conlan: ajoutez un guide au nom du fichier.
Rabih Kodeih

41

Juste une petite remarque. La réponse tvon fonctionne mais, si vous travaillez sur Windows, vous voudrez probablement open()le fichier avec 'rb'. Comme ça:

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 vous obtiendrez votre fichier tronqué au premier 0x1Aoctet.


1
Merci, j'ai tendance à oublier ces détails de bas niveau auxquels nous sommes confrontés.
mike_k

fml ... que se passe-t-il lorsque ce paramètre est transmis sur une machine Linux?
DMac the Destroyer

1
répondu à ma propre question ... désolé pour le spam. trouvé de la documentation ici . "Sous Unix, il n'est pas inutile d'ajouter un" b "au mode, vous pouvez donc l'utiliser indépendamment de la plate-forme pour tous les fichiers binaires."
DMac the Destroyer

16

Voici une méthode qui fonctionne bien et vous permet également de convertir le fichier dans un certain format (pour éviter l'erreur "impossible d'écrire en mode P au format 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)

où image est le django ImageField ou your_model_instance.image voici un exemple d'utilisation:

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

J'espère que cela t'aides


12

Ok, si tout ce que vous devez faire est d'associer le chemin du fichier image déjà existant à ImageField, alors cette solution peut être utile:

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

Eh bien, si vous êtes sérieux, le fichier image déjà existant ne sera pas associé à ImageField, mais la copie de ce fichier sera créée dans le répertoire upload_to comme 'imgfilename.jpg' et sera associée à ImageField.


2
Ne devez-vous pas l'ouvrir en tant que fichier binaire?
Mariusz Jamro

Tout comme @MariuszJamro l'a dit, cela devrait être comme ceci:with open('/path/to/already/existing/file', 'rb') as f:
rahmatns

n'oubliez pas non plus de sauvegarder l'objet:obj.save()
rahmatns

11

Ce que j'ai fait était de créer mon propre stockage qui ne sauvera tout simplement pas le fichier sur le disque:

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

Ensuite, dans mes modèles, pour mon ImageField, j'ai utilisé le nouveau stockage personnalisé:

from custom_storage import CustomStorage

custom_store = CustomStorage()

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

7

Si vous souhaitez simplement "définir" le nom de fichier réel, sans encourir la surcharge de chargement et de ré-enregistrement du fichier (!!), ou sans recourir à un champ de caractères (!!!), vous voudrez peut-être essayer quelque chose comme ça - -

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

Cela éclairera votre model_instance.myfile.url et tous les autres comme si vous aviez réellement téléchargé le fichier.

Comme le dit @ t-stone, ce que nous voulons vraiment, c'est pouvoir définir instance.myfile.path = 'mon-nom de fichier.jpg', mais Django ne le supporte pas actuellement.


Si model_instance est l'instance du modèle qui contient le fichier .. que représente l'autre "instance" ??
h3.

7

La solution la plus simple à mon avis:

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

Beaucoup de ces réponses étaient dépassées, et j'ai passé de nombreuses heures dans la frustration (je suis assez nouveau pour Django et le développement web en général). Cependant, j'ai trouvé cet excellent résumé par @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

Ce n'est peut-être pas la réponse que vous recherchez. mais vous pouvez utiliser charfield pour stocker le chemin du fichier au lieu d'ImageFile. De cette façon, vous pouvez associer par programme l'image téléchargée au champ sans recréer le fichier.


Oui, j'ai été tenté d'abandonner cela, et d'écrire directement sur MySQL, ou simplement d'utiliser un CharField ().
T. Stone

1

Tu peux essayer:

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

Travail! Vous pouvez enregistrer l'image en utilisant FileSystemStorage. consultez l'exemple ci-dessous

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)

Merci Nids, travailler absolument cette solution! Vous m'avez fait gagner beaucoup de temps :)
Mehmet Burak Ibis

0

Vous pouvez utiliser le framework Django REST et la bibliothèque de requêtes python pour enregistrer par programme l'image dans Django ImageField

Voici un exemple:

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

Avec Django 3, avec un modèle comme celui-ci:

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

si l'image a déjà été téléchargée, nous pouvons directement faire:

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()
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.