Django: เพิ่มรูปภาพใน ImageField จาก URL รูปภาพ


86

โปรดขอโทษสำหรับภาษาอังกฤษที่น่าเกลียดของฉัน ;-)

ลองนึกภาพแบบจำลองที่เรียบง่ายนี้:

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

ฉันต้องการสร้างรูปภาพจาก URL รูปภาพ (เช่นไม่ใช่ด้วยมือในไซต์ผู้ดูแลระบบ django)

ฉันคิดว่าฉันต้องทำสิ่งนี้:

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)

ฉันหวังว่าฉันจะอธิบายปัญหาได้ดีถ้าไม่บอกฉัน

ขอขอบคุณ :)

แก้ไข:

สิ่งนี้อาจใช้ได้ แต่ฉันไม่รู้วิธีแปลงcontentเป็นไฟล์ 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)

คำตอบ:


96

ฉันเพิ่งสร้าง http://www.djangosnippets.org/snippets/1890/สำหรับปัญหาเดียวกันนี้ โค้ดนี้คล้ายกับคำตอบที่ไม่มีเหตุผลข้างต้นยกเว้นว่าจะใช้ urllib2.urlopen เนื่องจาก urllib.urlretrieve ไม่ดำเนินการจัดการข้อผิดพลาดใด ๆ ตามค่าเริ่มต้นดังนั้นจึงง่ายต่อการรับเนื้อหาของหน้า 404/500 แทนที่จะเป็นสิ่งที่คุณต้องการ คุณสามารถสร้างฟังก์ชันการโทรกลับและคลาสย่อย URLOpener ที่กำหนดเองได้ แต่ฉันพบว่ามันง่ายกว่าแค่สร้างไฟล์ชั่วคราวของฉันเองดังนี้

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

3
บรรทัดสุดท้ายทำอะไร? สิ่งที่เป็นimวัตถุที่มาจากไหน?
นักบวช

1
@priestc: imค่อนข้างสั้น - ในตัวอย่างนั้นimเป็นอินสแตนซ์ของโมเดลและfileเป็นชื่อที่ไม่สามารถจินตนาการได้ของ FileField / ImageField บนอินสแตนซ์นั้น เอกสาร API จริงนี่คือสิ่งที่สำคัญ - เทคนิคนั้นควรใช้งานได้ทุกที่ที่คุณมีวัตถุ Django File ที่ผูกไว้กับวัตถุ: docs.djangoproject.com/en/1.5/ref/files/file/…
Chris Adams

27
ไม่จำเป็นต้องใช้ไฟล์ชั่วคราว ใช้requestsแทนการurllib2ที่คุณสามารถทำได้แล้วimage_content = ContentFile(requests.get(url_image).content) obj.my_image.save("foo.jpg", image_content)
สแตน

สแตน: คำขอทำให้ง่ายขึ้น แต่ IIRC ที่ซับเดียวจะเป็นปัญหาเว้นแต่คุณจะโทรหา Raise_for_status () ก่อนเพื่อหลีกเลี่ยงความสับสนกับข้อผิดพลาดหรือคำตอบที่ไม่สมบูรณ์
Chris Adams

อาจเป็นความคิดที่ดีที่จะปรับปรุงสิ่งนี้ให้ทันสมัยเนื่องจากเดิมเขียนในยุค Django 1.1 / 1.2 ที่กล่าวว่าฉันเชื่อว่า ContentFile ยังคงมีปัญหาที่จะโหลดไฟล์ทั้งหมดลงในหน่วยความจำดังนั้นการเพิ่มประสิทธิภาพที่ดีจะใช้ iter_content ที่มีขนาดก้อนที่เหมาะสม
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)


สวัสดีขอบคุณที่ช่วยฉัน;). ปัญหาคือ (ฉันอ้างถึงเอกสาร): "โปรดทราบว่าอาร์กิวเมนต์เนื้อหาต้องเป็นอินสแตนซ์ของไฟล์หรือคลาสย่อยของไฟล์" คุณมีวิธีแก้ปัญหาในการสร้างอินสแตนซ์ไฟล์ด้วยเนื้อหาของคุณหรือไม่?

ตรวจสอบการแก้ไขใหม่ ตอนนี้ควรเป็นตัวอย่างที่ใช้งานได้แล้ว (แม้ว่าฉันจะไม่ได้ทดสอบก็ตาม)
เหตุผล

แล้วโมเดลของฉันล่ะที่ฉันมีสาขาอื่นด้วย เช่น url ฯลฯ เป็นต้นหาก id ทำ model.image.save (... ) ฉันจะบันทึกช่องอื่น ๆ ได้อย่างไร พวกเขาไม่สามารถแก้ไขเป็นโมฆะได้: มันเป็นแบบนี้เหรอ? >>> car.photo.save ('myphoto.jpg', เนื้อหา, บันทึก = เท็จ) >>> car.save ()
แฮร์รี่

self.url ?? ... self.url ที่นี่คืออะไร ??
DeadDjangoDjoker

@DeadDjangoDjoker ไม่ทราบคุณต้องถามคนที่แก้ไขคำตอบของฉัน คำตอบนี้มีอายุ 5 ปี ณ จุดนี้ ฉันได้ย้อนกลับไปใช้วิธีแก้ปัญหา "ที่ใช้งานได้" ก่อนหน้านี้สำหรับคนรุ่นหลัง แต่คุณควรตอบตามคำตอบของ Chris Adam โดยสุจริต
ไร้สาระ

13

การรวมสิ่งที่ Chris Adams และ Stan พูดและอัปเดตสิ่งต่างๆให้ทำงานบน Python 3 หากคุณติดตั้งคำขอคุณสามารถทำสิ่งนี้ได้:

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)

เอกสารที่เกี่ยวข้องเพิ่มเติมในตัวอย่างการดาวน์โหลดไฟล์เอกสาร ContentFileและคำขอของ Django


5

ImageFieldเป็นเพียงสตริงเส้นทางที่สัมพันธ์กับMEDIA_ROOTการตั้งค่าของคุณ เพียงบันทึกไฟล์ (คุณอาจต้องการใช้ PIL เพื่อตรวจสอบว่าเป็นรูปภาพ) และเติมข้อมูลในฟิลด์ด้วยชื่อไฟล์

ดังนั้นจึงแตกต่างจากรหัสของคุณตรงที่คุณต้องบันทึกผลลัพธ์ของurllib.urlopenไฟล์ของคุณ(ภายในตำแหน่งสื่อของคุณ) หาเส้นทางบันทึกลงในโมเดลของคุณ


5

ฉันใช้วิธีนี้กับ Python 3 ซึ่งควรทำงานร่วมกับการปรับเปลี่ยนอย่างง่ายบน Python 2 ซึ่งขึ้นอยู่กับความรู้ของฉันว่าไฟล์ที่ฉันกำลังดึงมีขนาดเล็ก ถ้าไม่ใช่ของคุณฉันอาจแนะนำให้เขียนคำตอบลงในไฟล์แทนการบัฟเฟอร์ในหน่วยความจำ

ต้องใช้ BytesIO เนื่องจาก Django เรียกการค้นหา () บนอ็อบเจ็กต์ไฟล์และการตอบสนอง urlopen ไม่รองรับการค้นหา คุณสามารถส่งผ่านวัตถุไบต์ที่ส่งคืนโดย read () ไปยัง ContentFile ของ Django แทน

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

ไฟล์ (io) ส่งคืน <File: None>
Susaj S Nair

@SusajSNair นั่นเป็นเพียงเพราะไฟล์ไม่มีชื่อและ__repr__วิธีการFileเขียนชื่อ หากคุณต้องการคุณสามารถตั้งค่าnameแอตทริบิวต์บนFileวัตถุหลังจากสร้างวัตถุได้File(io)แต่จากประสบการณ์ของฉันมันไม่สำคัญ (นอกเหนือจากการทำให้ดูดีขึ้นหากคุณพิมพ์ออกมา) ymmv.
jbg

3

เมื่อเร็ว ๆ นี้ฉันใช้แนวทางต่อไปนี้ใน python 3 และ Django 3 บางทีนี่อาจจะน่าสนใจสำหรับคนอื่น ๆ เช่นกัน มันคล้ายกับวิธีแก้ปัญหาของ Chris Adams แต่สำหรับฉันมันไม่ได้ผลอีกต่อไป

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

นี่เป็นวิธีเดียวที่ใช้ได้ผลสำหรับฉัน (Python 3 + Django 2) ความคิดเห็นที่ไม่สำคัญเพียงอย่างเดียวคือชื่อฐานอาจไม่มีนามสกุลที่ถูกต้องในทุกกรณีขึ้นอยู่กับ URL
PhysicsAI

2

เพิ่งค้นพบว่าคุณไม่จำเป็นต้องสร้างไฟล์ชั่วคราว:

สตรีมเนื้อหา url โดยตรงจาก django ไปยัง minio

ฉันต้องจัดเก็บไฟล์ของฉันใน minio และมี django docker container โดยไม่ต้องใช้เนื้อที่ดิสก์มากนักและจำเป็นต้องดาวน์โหลดไฟล์วิดีโอขนาดใหญ่สิ่งนี้จึงมีประโยชน์กับฉันมาก


-5

นี่เป็นวิธีที่ถูกต้องและใช้ได้ผล

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

14
นั่นไม่ใช่วิธีที่ถูกต้องคุณหลีกเลี่ยงกลไกการจัดเก็บไฟล์ทั้งหมดของ FielField และไม่ต้องคำนึงถึง API การจัดเก็บ
Andre Bossard
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.