Django: afbeelding toevoegen in een ImageField vanaf afbeeldings-url

excuseer me voor mijn lelijke Engels 😉

Stel je dit zeer eenvoudige model voor:

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

Ik wil graag een foto maken van een afbeeldings-URL (d.w.z. niet met de hand op de django-beheerderssite).

Ik denk dat ik zoiets als dit moet doen:

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)

Ik hoop dat ik het probleem goed heb uitgelegd, zo niet, vertel het me dan.

Bedankt 🙂

Bewerken:

Dit kan werken, maar ik weet niet hoe ik contentnaar een django-bestand moet converteren:

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)

Antwoord 1, autoriteit 100%

Ik heb zojuist http://www.djangosnippets.org/snippets/1890/gemaakt voor dit zelfde probleem. De code is vergelijkbaar met het antwoord van pithyless hierboven, behalve dat het urllib2.urlopen gebruikt omdat urllib.urlretrieve standaard geen foutafhandeling uitvoert, dus het is gemakkelijk om de inhoud van een 404/500-pagina te krijgen in plaats van wat u nodig had. U kunt een callback-functie & aangepaste URLOpener-subklasse, maar ik vond het gemakkelijker om mijn eigen tijdelijke bestand als volgt te maken:

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

Antwoord 2, autoriteit 33%


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)


Antwoord 3, autoriteit 15%

Combineren wat Chris Adams en Stan zeiden en dingen bijwerken om te werken op Python 3, als je installeert Verzoekenje kunt zoiets als dit doen:

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)

Meer relevante documenten in Django’s ContentFile-documentatieen voorbeeld van bestandsdownload van verzoeken.


Antwoord 4, autoriteit 6%

ImageFieldis slechts een tekenreeks, een pad ten opzichte van uw MEDIA_ROOT-instelling. Sla het bestand gewoon op (u kunt PIL gebruiken om te controleren of het een afbeelding is) en vul het veld met de bestandsnaam.

Dus het verschilt van uw code in die zin dat u de uitvoer van uw urllib.urlopenmoet opslaan in een bestand (in uw medialocatie), het pad moet bepalen en dat op uw model moet opslaan.


Antwoord 5, autoriteit 5%

Ik doe het op deze manier op Python 3, wat zou moeten werken met eenvoudige aanpassingen op Python 2. Dit is gebaseerd op mijn kennis dat de bestanden die ik ophaal klein zijn. Als de jouwe dat niet is, raad ik je aan om het antwoord naar een bestand te schrijven in plaats van in het geheugen te bufferen.

BytesIO is nodig omdat Django seek() aanroept op het bestandsobject en urlopen-antwoorden zoeken niet ondersteunen. Je zou in plaats daarvan het door read() geretourneerde object bytes kunnen doorgeven aan Django’s ContentFile.

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

Antwoord 6, autoriteit 3%

Onlangs gebruik ik de volgende aanpak binnen python 3 en Django 3, misschien is dit ook interessant voor anderen. Het is vergelijkbaar met de oplossing van Chris Adams, maar voor mij werkte het niet meer.

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

Antwoord 7, autoriteit 2%

Net ontdekt dat je geen tijdelijk bestand hoeft te genereren:

Stream url-inhoud rechtstreeks van django naar minio

Ik moet mijn bestanden in minio opslaan en heb django docker-containers zonder veel schijfruimte en moet grote videobestanden downloaden, dus dit was erg handig voor mij.


Antwoord 8

Het is bijna 11 jaar geleden sinds de vraag en het meest bekende antwoord is gepost. Met dank aan @chris-adams voor de reactie. Ik plaats hetzelfde antwoord opnieuw samen met de bijgewerkte pakketten en ondersteuning.

#! /usr/bin/python3
# lib/utils.py
import urllib3                                          # http Request Package.
from typing import Optional
from django.core.files import File                      # Handle Files in Django
from django.core.files.temp import NamedTemporaryFile   # handling temporary files.
def fetch_image(url: str, instance: models.Model, field: str, name: Optional[str]=None):
    """
    fetch_image Fetches an image URL and adds it to the model field.
    the parameter instance does not need to be a saved instance. 
    :url: str = A valid image URL.
    :instance: django.db.models.Model = Expecting a model with image field or file field. 
    :field: str = image / file field name as string; 
    [name:str] = Preferred file name, such as product slug or something.  
    :return: updated instance as django.db.models.Model, status of updation as bool.
    """
    conn = urllib3.PoolManager()
    response = conn.request('GET', url)
    if response.status <> 200:
        print("[X] 404! IMAGE NOT FOUND")
        print(f"TraceBack: {url}")
        return instance, False
    file_obj = NamedTemporaryFile(delete=True)
    file_obj.write( response.data )
    file_obj.flush()
    img_format = url.split('.')[-1]
    if name is None:
        name = url.split('/')[-1]
    if not name.endswith(img_format):
        name += f'.{img_format}'
    django_file_obj = File(file_obj)
    (getattr(instance, field)).save(name, django_file_obj)
    return instance, True

Getest met Django==2.2.12 in Python 3.7.5


if __name__ == '__main__':
    instance = ProductImage()
    url = "https://www.publicdomainpictures.net/pictures/320000/velka/background-image.png"
    instance, saved = fetch_image(url, instance, field='banner_image', name='intented-image-slug')
    status = ["FAILED! ", "SUCCESS! "][saved]
    print(status, instance.banner_image and instance.banner_image.path)
    instance.delete()

Antwoord 9

dit is de juiste en werkende manier

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

Other episodes