Django: ajouter une image dans un ImageField à partir de l'url de l'image

85

excusez-moi pour mon anglais laid ;-)

Imaginez ce modèle très simple:

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

Je voudrais créer une photo à partir d'une URL d'image (c'est-à-dire pas à la main sur le site d'administration de django).

Je pense que je dois faire quelque chose comme ça:

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)

J'espère avoir bien expliqué le problème, sinon dites-moi.

Merci :)

Éditer :

Cela peut fonctionner mais je ne sais pas comment convertir contentun fichier 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)

la source

Réponses:

96

Je viens de créer http://www.djangosnippets.org/snippets/1890/ pour ce même problème. Le code est similaire à la réponse pithyless ci-dessus sauf qu'il utilise urllib2.urlopen car urllib.urlretrieve n'effectue aucune gestion des erreurs par défaut, il est donc facile d'obtenir le contenu d'une page 404/500 au lieu de ce dont vous aviez besoin. Vous pouvez créer une fonction de rappel et une sous-classe URLOpener personnalisée, mais j'ai trouvé plus simple de créer mon propre fichier temporaire comme ceci:

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
la source
3
que fait la dernière ligne? d'où vient l' imobjet?
Priestc
1
@priestc: imétait un peu laconique - dans cet exemple, imétait l'instance de modèle et fileétait le nom sans imagination d'un FileField / ImageField sur cette instance. La documentation de l'API réelle ici est ce qui compte - cette technique devrait fonctionner partout où vous avez un objet Django File lié à un objet: docs.djangoproject.com/en/1.5/ref/files/file
Chris Adams
26
Un fichier temporaire est inutile. En utilisant requestsau lieu de urllib2vous pouvez faire: image_content = ContentFile(requests.get(url_image).content)et puis obj.my_image.save("foo.jpg", image_content).
Stan
Stan: demandes ne simplifie cela , mais IIRC que uniligne serait un problème à moins que vous avez appelé raise_for_status () premier à confusion éviter des erreurs ou des réponses incomplètes
Chris Adams
Ce serait probablement une bonne idée de le moderniser car il a été écrit à l'origine dans l'ère Django 1.1 / 1.2. Cela dit, je pense que ContentFile a toujours le problème de charger le fichier entier en mémoire, donc une bonne optimisation consisterait à utiliser iter_content avec une taille de bloc raisonnable.
Chris Adams
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)

sans pierre
la source
Salut, merci de m'aider;). Le problème est (je cite le document): "Notez que l'argument de contenu doit être une instance de File ou d'une sous-classe de File." Alors, avez-vous une solution pour créer une instance de fichier avec votre contenu?
Vérifiez la nouvelle modification; cela devrait maintenant être un exemple fonctionnel (même si je ne l'ai pas testé)
pithyless
Qu'en est-il de mon modèle, où j'ai également d'autres domaines. Comme url, etc, etc. Si id, faites model.image.save (...). comment enregistrer les autres champs? Ils ne peuvent pas être nuls EDIT: serait-ce quelque chose comme ça? >>> car.photo.save ('myphoto.jpg', contents, save = False) >>> car.save ()
Harry
self.url ?? ... qu'est-ce que self.url ici ??
DeadDjangoDjoker
@DeadDjangoDjoker Aucune idée, vous devriez demander à la personne qui a édité ma réponse. Cette réponse a 5 ans à ce stade; Je suis revenu à la solution «de travail» précédente pour la postérité, mais honnêtement, vous êtes mieux avec la réponse de Chris Adam.
pierre
13

En combinant ce que Chris Adams et Stan ont dit et en mettant à jour les choses pour fonctionner sur Python 3, si vous installez Requests, vous pouvez faire quelque chose comme ceci:

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)

Des documents plus pertinents dans la documentation ContentFile de Django et l' exemple de téléchargement de fichiers de requêtes .

metakermit
la source
5

ImageFieldest juste une chaîne, un chemin relatif à votre MEDIA_ROOTparamètre. Enregistrez simplement le fichier (vous voudrez peut-être utiliser PIL pour vérifier qu'il s'agit d'une image) et remplissez le champ avec son nom de fichier.

Il diffère donc de votre code en ce que vous devez enregistrer la sortie de votre urllib.urlopenfichier dans (à l'intérieur de votre emplacement multimédia), déterminer le chemin, l'enregistrer dans votre modèle.

Oli
la source
5

Je le fais de cette façon sur Python 3, qui devrait fonctionner avec de simples adaptations sur Python 2. Ceci est basé sur ma connaissance que les fichiers que je récupère sont petits. Si ce n'est pas le cas, je recommanderais probablement d'écrire la réponse dans un fichier au lieu de la mettre en mémoire tampon.

BytesIO est nécessaire car Django appelle seek () sur l'objet fichier et les réponses urlopen ne prennent pas en charge la recherche. Vous pouvez passer l'objet bytes renvoyé par read () au ContentFile de Django à la place.

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
la source
File (io) renvoie <File: None>
Susaj S Nair
@SusajSNair C'est simplement parce que le fichier n'a pas de nom et que la __repr__méthode d' Fileécriture du nom. Si vous préférez, vous pouvez définir l' nameattribut sur l' Fileobjet après l'avoir créé avec File(io), mais d'après mon expérience, cela n'a pas d'importance (à part le rendre plus joli si vous l'imprimez). ymmv.
jbg
3

Récemment, j'utilise l'approche suivante dans python 3 et Django 3, peut-être que cela pourrait être intéressant pour d'autres également. C'est similaire à la solution Chris Adams mais pour moi cela ne fonctionnait plus.

# -*- coding: utf-8 -*-
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.attribute_a = True
new_image.attribute_b = 'False'
new_image.file = SimpleUploadedFile(basename, open(tmpfile, "rb").read())
new_image.save()
contmp
la source
C'est la seule solution qui a fonctionné pour moi (Python 3 + Django 2). Le seul commentaire quelque peu trivial est que le nom de base peut ne pas avoir l'extension correcte dans tous les cas, selon l'URL.
physicsAI
1

Je viens de découvrir que vous n'avez pas à générer de fichier temporaire:

Diffuser du contenu URL directement de django vers minio

Je dois stocker mes fichiers dans minio et avoir des conteneurs django docker sans beaucoup d'espace disque et j'ai besoin de télécharger de gros fichiers vidéo, donc cela m'a vraiment aidé.

éphes
la source
-5

c'est la bonne manière de travailler

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ç
la source
14
Cela ne peut pas être la bonne façon, vous contournez tout le mécanisme de stockage de fichiers du FielField et ne payez aucun respect à l'API de stockage.
Andre Bossard