Django: thêm hình ảnh vào ImageField từ url hình ảnh


85

xin thứ lỗi vì tiếng anh xấu xí của tôi ;-)

Hãy tưởng tượng mô hình rất đơn giản này:

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

Tôi muốn tạo Ảnh từ URL hình ảnh (tức là không phải bằng tay trong trang web quản trị django).

Tôi nghĩ rằng tôi cần phải làm điều gì đó như sau:

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)

Tôi hy vọng rằng tôi đã giải thích rõ vấn đề, nếu không nói với tôi.

Cảm ơn bạn :)

Biên tập :

Điều này có thể hoạt động nhưng tôi không biết cách chuyển đổi contentsang tệp 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)

Câu trả lời:


96

Tôi vừa tạo http://www.djangosnippets.org/snippets/1890/ cho cùng một vấn đề này. Mã này tương tự như câu trả lời của pithyless 'ở trên ngoại trừ nó sử dụng urllib2.urlopen vì urllib.urlretrieve không thực hiện bất kỳ xử lý lỗi nào theo mặc định, vì vậy, thật dễ dàng để lấy nội dung của trang 404/500 thay vì những gì bạn cần. Bạn có thể tạo hàm gọi lại và lớp con URLOpener tùy chỉnh nhưng tôi thấy việc tạo tệp tạm thời của riêng mình như thế này dễ dàng hơn:

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
dòng cuối cùng đang làm gì? là những gì imđối tượng đến từ đâu?
Priestc

1
@priestc: imhơi ngắn - trong ví dụ đó, imlà trường hợp mô hình và filelà tên không tưởng của một FileField / ImageField trên trường hợp đó. Các tài liệu API thực tế đây là những gì quan trọng - đó là kỹ thuật nên làm việc bất cứ nơi nào bạn có một đối tượng tập tin Django bị ràng buộc vào một đối tượng: docs.djangoproject.com/en/1.5/ref/files/file/...
Chris Adams

26
Một tệp tạm thời là không cần thiết. Sử dụng requeststhay vì urllib2bạn có thể làm: image_content = ContentFile(requests.get(url_image).content)và sau đó obj.my_image.save("foo.jpg", image_content).
Stan

Stan: yêu cầu không đơn giản hóa đó nhưng IIRC rằng một-liner sẽ là một vấn đề, trừ khi bạn gọi raise_for_status () đầu tiên để tránh nhầm lẫn với các lỗi hoặc phản ứng không đầy đủ
Chris Adams

Có lẽ sẽ là một ý tưởng hay để hiện đại hóa điều này vì nó ban đầu được viết trong kỷ nguyên Django 1.1 / 1.2. Điều đó nói rằng, tôi tin rằng ContentFile vẫn còn vấn đề là nó sẽ tải toàn bộ tệp vào bộ nhớ vì vậy, một cách tối ưu hóa tốt đẹp sẽ là sử dụng iter_content với kích thước phân đoạn hợp lý.
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)


Chào bạn, cảm ơn bạn đã giúp tôi;). Vấn đề là (tôi trích dẫn tài liệu): "Lưu ý rằng đối số nội dung phải là một thể hiện của Tệp hoặc một lớp con của Tệp." Vậy bạn có giải pháp nào để tạo File instance với nội dung của mình không?

Kiểm tra bản chỉnh sửa mới; bây giờ điều này phải là một ví dụ làm việc (mặc dù tôi đã không kiểm tra nó)
pithyless

Còn về mô hình của tôi, nơi tôi cũng có các lĩnh vực khác. Như url, v.v., v.v. Nếu id do model.image.save (...). làm cách nào để lưu các trường khác? Họ không thể là null EDIT: woudl nó là một cái gì đó như thế này? >>> car.photo.save ('myphoto.jpg', nội dung, save = False) >>> car.save ()
Harry

self.url ?? ... self.url ở đây là gì ??
DeadDjangoDjoker

@DeadDjangoDjoker Không có ý tưởng, bạn phải hỏi người đã chỉnh sửa câu trả lời của tôi. Câu trả lời này là 5 tuổi vào thời điểm này; Tôi đã hoàn nguyên về giải pháp "làm việc" trước đây cho hậu thế, nhưng thành thật mà nói thì bạn nên chấp nhận câu trả lời của Chris Adam.
pithyless

13

Kết hợp những gì Chris Adams và Stan đã nói và cập nhật những thứ để hoạt động trên Python 3, nếu bạn cài đặt Yêu cầu, bạn có thể làm điều gì đó như sau:

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)

Các tài liệu liên quan khác trong tài liệu ContentFile của Django và ví dụ tải xuống tệp của các Yêu cầu .


5

ImageFieldchỉ là một chuỗi, một đường dẫn liên quan đến MEDIA_ROOTcài đặt của bạn . Chỉ cần lưu tệp (bạn có thể muốn sử dụng PIL để kiểm tra xem nó có phải là hình ảnh không) và điền vào trường bằng tên tệp của nó.

Vì vậy, nó khác với mã của bạn ở chỗ bạn cần lưu đầu ra của bạn urllib.urlopenvào tệp (bên trong vị trí phương tiện của bạn), tìm ra đường dẫn, lưu nó vào mô hình của bạn.


5

Tôi làm theo cách này trên Python 3, sẽ hoạt động với các bản chuyển thể đơn giản trên Python 2. Điều này dựa trên kiến ​​thức của tôi rằng các tệp tôi đang truy xuất là nhỏ. Nếu không, tôi có thể khuyên bạn nên viết phản hồi ra tệp thay vì lưu vào bộ nhớ.

BytesIO là cần thiết vì Django gọi seek () trên đối tượng tệp và phản hồi urlopen không hỗ trợ tìm kiếm. Thay vào đó, bạn có thể chuyển đối tượng byte được read () trả về vào ContentFile của 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))

Tệp (io) lợi nhuận <tập tin: Không>
Susaj S Nair

@SusajSNair Đó chỉ là vì tệp không có tên và __repr__phương thức Fileviết tên. Nếu muốn, bạn có thể đặt namethuộc tính trên Fileđối tượng sau khi tạo nó bằng File(io), nhưng theo kinh nghiệm của tôi thì điều đó không quan trọng (ngoài việc làm cho nó trông đẹp hơn nếu bạn in nó ra). ymmv.
jbg

3

Gần đây, tôi sử dụng cách tiếp cận sau trong python 3 và Django 3, có lẽ điều này cũng có thể thú vị đối với những người khác. Nó tương tự như giải pháp của Chris Adams nhưng đối với tôi thì nó không hoạt động nữa.

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

Đây là giải pháp duy nhất phù hợp với tôi (Python 3 + Django 2). Chỉ có một nhận xét nhỏ là tên cơ sở có thể không có phần mở rộng chính xác trong mọi trường hợp, tùy thuộc vào URL.
vật lýAI

1

Chỉ phát hiện ra rằng bạn không phải tạo tệp tạm thời:

Truyền trực tiếp nội dung url từ django sang minio

Tôi phải lưu trữ các tệp của mình trong minio và có bộ chứa django docker mà không có nhiều dung lượng đĩa và cần tải xuống các tệp video lớn, vì vậy điều này thực sự hữu ích đối với tôi.


-5

đây là cách làm việc đúng đắn

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
Đó không thể là cách đúng đắn, bạn phá vỡ toàn bộ cơ chế lưu trữ tệp của FielField và không coi trọng api lưu trữ.
Andre Bossard
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.