Django:通过图片网址在ImageField中添加图片


85

请原谅我英语不好;-)

想象一下这个非常简单的模型:

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)

Answers:


96

我只是针对同一问题创建了http://www.djangosnippets.org/snippets/1890/。该代码与上面的pithyless'答案相似,不同之处在于它使用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物体是什么来的?
priestc 2014年

1
@priestc:im有点简洁-在该示例中,im是模型实例,并且file是该实例上FileField / ImageField的虚构名称。实际的API文档很重要–该技术应该在将Django File对象绑定到对象的任何地方都有效:docs.djangoproject.com/en/1.5/ref/files/file / ...
Chris Adams,

26
不需要临时文件。使用requests代替urllib2您可以执行以下操作:image_content = ContentFile(requests.get(url_image).content)然后obj.my_image.save("foo.jpg", image_content)
Stan

斯坦(Stan):要求确实简化了这一点,但是IIRC认为,除非您先调用raise_for_status()以避免出现错误或不完整的响应,否则单线将是一个问题
克里斯·亚当斯

对它进行现代化改造可能是个好主意,因为它最初是在Django 1.1 / 1.2时代编写的。就是说,我相信ContentFile仍然存在将整个文件加载到内存中的问题,因此一个很好的优化方法是使用具有合理块大小的iter_content。
克里斯·亚当斯

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)


嗨,谢谢您对我的帮助;)。问题是(我引用了doc):“请注意content参数必须是File的实例或File的子类。” 那么,您是否有解决方案来使用您的内容创建File实例?

检查新的编辑;这现在应该是一个工作示例(尽管我尚未测试过)
令人讨厌的

那我的模型又如何呢?例如url等,如果是id,请执行model.image.save(...)。如何保存其他字段?他们不能为null编辑:会是这样吗?>>> car.photo.save('myphoto.jpg',contents,save = False)>>> car.save()
哈利2010年

self.url ?? ...什么是self.url ??
DeadDjangoDjoker,2015年

@DeadDjangoDjoker不知道,您必须问编辑我答案的人。答案是5岁。我已经恢复了先前的“工作”解决方案,但实际上,克里斯·亚当(Chris Adam)的回答会更好。
2015年

13

结合Chris Adams和Stan所说的内容,并更新其内容以在Python 3上运行,如果您安装Requests,则可以执行以下操作:

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)

Django的ContentFile文档Requests的文件下载示例中的更多相关文档。


5

ImageField 只是一个字符串,相对于您的路径 MEDIA_ROOT设置。只需保存文件(您可能需要使用PIL来检查它是图像),然后使用文件名填充该字段。

因此,它与代码不同之处在于,您需要将输出保存urllib.urlopen到文件中(在媒体位置内部),确定路径,然后将其保存到模型中。


5

我是在Python 3上这样做的,应该可以在Python 2上进行简单的修改。这是基于我的知识,即我正在检索的文件很小。如果不是,我可能建议将响应写出到文件中,而不是在内存中进行缓冲。

需要BytesIO,因为Django在文件对象上调用seek(),而urlopen响应不支持搜索。您可以将read()返回的bytes对象传递给Django的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))

File(io)返回<File:None>
Susaj S Nair19年

@SusajSNair那只是因为文件没有名称,并且__repr__用于File写入名称的方法。如果愿意,可以在使用创建对象后nameFile对象上设置属性File(io),但是根据我的经验,这并不重要(除了打印出来看起来更好之外)。嗯
jbg

3

最近,我在python 3和Django 3中使用了以下方法,也许这对于其他人也可能很有趣。它类似于Chris Adams的解决方案,但对我而言,它不再起作用。

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

这是唯一对我有效的解决方案(Python 3 + Django 2)。只是有些琐碎的注释是,在每种情况下,根据URL的不同,基本名称可能没有正确的扩展名。
physicsAI

1

刚刚发现您不必生成临时文件:

将url内容直接从Django流到minio

我必须将文件存储在minio中,并且要有没有太多磁盘空间的django docker容器,并且需要下载大的视频文件,因此这对我真的很有帮助。


-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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.