【问题标题】:Programmatically saving image to Django ImageField以编程方式将图像保存到 Django ImageField
【发布时间】:2010-11-21 10:59:36
【问题描述】:

好的,我已经尝试了几乎所有的东西,但我无法让它工作。

  • 我有一个带有 ImageField 的 Django 模型
  • 我有通过 HTTP 下载图像的代码(经过测试且有效)
  • 图像直接保存到“upload_to”文件夹中(upload_to 是在 ImageField 上设置的那个)
  • 我需要做的就是将已经存在的图像文件路径与 ImageField 关联起来

我用 6 种不同的方式编写了这段代码。

我遇到的问题是我编写的所有代码都会导致以下行为: (1) Django 将创建第二个文件,(2) 重命名新文件,在文件名的末尾添加一个 _,然后 (3) 不传输任何数据,而将其基本上保留为一个空的重命名文件。 'upload_to' 路径中剩下 2 个文件,一个是实际图像,一个是图像名称,但为空,当然 ImageField 路径设置为 Django 尝试创建的空文件.

如果不清楚,我将尝试说明:

## Image generation code runs.... 
/Upload
     generated_image.jpg     4kb

## Attempt to set the ImageField path...
/Upload
     generated_image.jpg     4kb
     generated_image_.jpg    0kb

ImageField.Path = /Upload/generated_image_.jpg

如何在不让 Django 尝试重新存储文件的情况下做到这一点?我真正想要的是这种效果......

model.ImageField.path = generated_image_path

...但是这当然行不通。

是的,我已经解决了这里的其他问题,例如 this one 以及 File 上的 django 文档

更新 经过进一步测试,它只有在 Windows Server 上的 Apache 下运行时才会出现这种行为。在 XP 上的“运行服务器”下运行时,它不会执行此行为。

我被难住了。

这是在 XP 上成功运行的代码...

f = open(thumb_path, 'r')
model.thumbnail = File(f)
model.save()

【问题讨论】:

  • 另一个很棒的 Django 问题。我已经做了几次尝试来解决这个问题,但没有成功。在上传目录中创建的文件已损坏,与原始文件(存储在其他位置)相比,其大小只有一小部分。
  • 您的更新不起作用

标签: python django django-models


【解决方案1】:

我有一些代码可以从网络上获取图像并将其存储在模型中。重要的一点是:

from django.core.files import File  # you need this somewhere
import urllib


# The following actually resides in a method of my model

result = urllib.urlretrieve(image_url) # image_url is a URL to an image

# self.photo is the ImageField
self.photo.save(
    os.path.basename(self.url),
    File(open(result[0], 'rb'))
    )

self.save()

这有点令人困惑,因为它从我的模型中提取出来并且有点脱离上下文,但重要的部分是:

  • 从网络上提取的图像存储在upload_to文件夹中,而是通过urllib.urlretrieve()存储为临时文件,随后被丢弃。
  • ImageField.save() 方法接受一个文件名(os.path.basename 位)和一个 django.core.files.File 对象。

如果您有任何问题或需要澄清,请告诉我。

编辑:为了清楚起见,这里是模型(减去任何必需的导入语句):

class CachedImage(models.Model):
    url = models.CharField(max_length=255, unique=True)
    photo = models.ImageField(upload_to=photo_path, blank=True)

    def cache(self):
        """Store image locally if we have a URL"""

        if self.url and not self.photo:
            result = urllib.urlretrieve(self.url)
            self.photo.save(
                    os.path.basename(self.url),
                    File(open(result[0], 'rb'))
                    )
            self.save()

【讨论】:

  • tvon -- 我已经尝试过实现这种效果,但也许我会再试一次,事实上,我的代码看起来与此非常相似。 (即使断章取义,我也能看到它是如何工作的)。
  • 我建议也使用 url 解析来避免将 url paramatar gunk 附加到图像上。 import urlparseos.path.basename(urlparse.urlparse(self.url).path)。感谢您的帖子,很有帮助。
  • 我得到 django.core.exceptions.SuspiciousOperation: Attempted access to '/images/10.jpg' denied.
  • @DataGreed 您应该从模型中的 upload_to 定义中删除正斜杠“/”。已解决here
  • 我收到这样的错误:prohibited to prevent data loss due to unsaved related object 'stream'.
【解决方案2】:

如果尚未创建模型,则超级简单:

首先,将您的图片文件复制到上传路径(假设 = 'path/' 在下面的 sn-p 中)。

第二,使用类似:

class Layout(models.Model):
    image = models.ImageField('img', upload_to='path/')

layout = Layout()
layout.image = "path/image.png"
layout.save()

在 django 1.4 中测试和工作,它可能也适用于现有模型。

【讨论】:

  • 这是正确答案,需要更多投票!!!也找到了这个解决方案here
  • 嗨。我有个问题。我将 django-storages 与 Amazon S3 后端一起使用。这会触发新的上传吗?
  • @SalvatoreIovene 我正在使用 django-storages 和 S3 这样做,它不会触发新的上传:)
  • Django 有一些现有的逻辑来解释磁盘上的重复文件名。此方法破坏了该逻辑,因为用户需要检查文件名重复。
  • @Conlan:将 guid 附加到文件名。
【解决方案3】:

只是一点点评论。 tvon 回答有效,但是,如果您在 Windows 上工作,您可能希望 open() 使用 'rb' 的文件。像这样:

class CachedImage(models.Model):
    url = models.CharField(max_length=255, unique=True)
    photo = models.ImageField(upload_to=photo_path, blank=True)

    def cache(self):
        """Store image locally if we have a URL"""

        if self.url and not self.photo:
            result = urllib.urlretrieve(self.url)
            self.photo.save(
                    os.path.basename(self.url),
                    File(open(result[0], 'rb'))
                    )
            self.save()

否则您的文件会在第一个 0x1A 字节处被截断。

【讨论】:

  • 谢谢,我往往会忘记窗口面对我们的这些低级细节。
  • fml... 在 linux 机器上传入该参数会发生什么?
  • 回答了我自己的问题...抱歉垃圾邮件。找到了这个here 的一些文档。 “在 Unix 上,将 'b' 附加到模式并没有什么坏处,因此您可以独立于平台使用它来处理所有二进制文件。”
  • 即使在 OSX 上读取二进制文件,这个 'rb' 也能像魔术一样工作!谢谢
【解决方案4】:

这是一种效果很好的方法,它也允许您将文件转换为某种格式(以避免“无法将模式 P 写入 JPEG”错误):

import urllib2
from django.core.files.base import ContentFile
from PIL import Image
from StringIO import StringIO

def download_image(name, image, url):
    input_file = StringIO(urllib2.urlopen(url).read())
    output_file = StringIO()
    img = Image.open(input_file)
    if img.mode != "RGB":
        img = img.convert("RGB")
    img.save(output_file, "JPEG")
    image.save(name+".jpg", ContentFile(output_file.getvalue()), save=False)

其中 image 是 django ImageField 或 your_model_instance.image 这是一个用法示例:

p = ProfilePhoto(user=user)
download_image(str(user.id), p.image, image_url)
p.save()

希望对你有帮助

【讨论】:

    【解决方案5】:

    好的,如果您只需将已经存在的图像文件路径与 ImageField 相关联,那么此解决方案可能会有所帮助:

    from django.core.files.base import ContentFile
    
    with open('/path/to/already/existing/file') as f:
      data = f.read()
    
    # obj.image is the ImageField
    obj.image.save('imgfilename.jpg', ContentFile(data))
    

    好吧,如果认真的话,已经存在的图像文件不会与 ImageField 关联,但该文件的副本将在 upload_to 目录中创建为“imgfilename.jpg”,并将与 ImageField 关联。

    【讨论】:

    • 你不应该把它作为二进制文件打开吗?
    • 就像@MariuszJamro 说的,应该是这样的:with open('/path/to/already/existing/file', 'rb') as f:
    • 也别忘了保存对象:obj.save()
    【解决方案6】:

    我所做的是创建自己的存储,不会将文件保存到磁盘:

    from django.core.files.storage import FileSystemStorage
    
    class CustomStorage(FileSystemStorage):
    
        def _open(self, name, mode='rb'):
            return File(open(self.path(name), mode))
    
        def _save(self, name, content):
            # here, you should implement how the file is to be saved
            # like on other machines or something, and return the name of the file.
            # In our case, we just return the name, and disable any kind of save
            return name
    
        def get_available_name(self, name):
            return name
    

    然后,在我的模型中,对于我的 ImageField,我使用了新的自定义存储:

    from custom_storage import CustomStorage
    
    custom_store = CustomStorage()
    
    class Image(models.Model):
        thumb = models.ImageField(storage=custom_store, upload_to='/some/path')
    

    【讨论】:

      【解决方案7】:

      另一种可能的方法:

      from django.core.files import File
      
      with open('path_to_file', 'r') as f:   # use 'rb' mode for python3
          data = File(f)
          model.image.save('filename', data, True)
      

      【讨论】:

        【解决方案8】:

        很多这些答案都已经过时了,我在沮丧中度过了很多小时(总的来说,我对 Django 和 web 开发还很陌生)。然而,我发现了@iambibhas 的这个极好的要点:https://gist.github.com/iambibhas/5051911

        import requests
        
        from django.core.files import File
        from django.core.files.temp import NamedTemporaryFile
        
        
        def save_image_from_url(model, url):
            r = requests.get(url)
        
            img_temp = NamedTemporaryFile(delete=True)
            img_temp.write(r.content)
            img_temp.flush()
        
            model.image.save("image.jpg", File(img_temp), save=True)
        
        

        【讨论】:

          【解决方案9】:

          如果您只想“设置”实际文件名,而不会产生加载和重新保存文件的开销 (!!) 或使用字符域 (!!!),您可能想尝试一下像这样——

          model_instance.myfile = model_instance.myfile.field.attr_class(model_instance, model_instance.myfile.field, 'my-filename.jpg')
          

          这将点亮您的 model_instance.myfile.url 和所有其余部分,就像您实际上传了文件一样。

          就像@t-stone 所说,我们真正想要的是能够设置 instance.myfile.path = 'my-filename.jpg',但 Django 目前不支持。

          【讨论】:

          • 如果 model_instance 是包含文件的模型的实例.. 另一个“实例”代表什么??
          【解决方案10】:

          这可能不是您正在寻找的答案。但您可以使用 charfield 来存储文件的路径,而不是 ImageFile。通过这种方式,您可以以编程方式将上传的图像与字段相关联,而无需重新创建文件。

          【讨论】:

          • 是的,我很想放弃这个,要么直接写入 MySQL,要么只使用 CharField()。
          【解决方案11】:

          使用 Django 3, 使用这样的模型:

          class Item(models.Model):
             name = models.CharField(max_length=255, unique=True)
             photo= models.ImageField(upload_to='image_folder/', blank=True)
          

          如果图片已经上传,我们可以直接这样做:

          Item.objects.filter(...).update(photo='image_folder/sample_photo.png')
          

          my_item = Item.objects.get(id=5)
          my_item.photo='image_folder/sample_photo.png'
          my_item.save()
          

          【讨论】:

            【解决方案12】:

            你可以试试:

            model.ImageField.path = os.path.join('/Upload', generated_image_path)
            

            【讨论】:

              【解决方案13】:
              class tweet_photos(models.Model):
              upload_path='absolute path'
              image=models.ImageField(upload_to=upload_path)
              image_url = models.URLField(null=True, blank=True)
              def save(self, *args, **kwargs):
                  if self.image_url:
                      import urllib, os
                      from urlparse import urlparse
                      file_save_dir = self.upload_path
                      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(file_save_dir, filename)
                      self.image_url = ''
                  super(tweet_photos, self).save()
              

              【讨论】:

                【解决方案14】:
                class Pin(models.Model):
                    """Pin Class"""
                    image_link = models.CharField(max_length=255, null=True, blank=True)
                    image = models.ImageField(upload_to='images/', blank=True)
                    title = models.CharField(max_length=255, null=True, blank=True)
                    source_name = models.CharField(max_length=255, null=True, blank=True)
                    source_link = models.CharField(max_length=255, null=True, blank=True)
                    description = models.TextField(null=True, blank=True)
                    tags = models.ForeignKey(Tag, blank=True, null=True)
                
                    def __unicode__(self):
                        """Unicode class."""
                        return unicode(self.image_link)
                
                    def save(self, *args, **kwargs):
                        """Store image locally if we have a URL"""
                        if self.image_link and not self.image:
                            result = urllib.urlretrieve(self.image_link)
                            self.image.save(os.path.basename(self.image_link), File(open(result[0], 'r')))
                            self.save()
                            super(Pin, self).save()
                

                【讨论】:

                  【解决方案15】:

                  工作! 您可以使用 FileSystemStorage 保存图像。 检查下面的示例

                  def upload_pic(request):
                  if request.method == 'POST' and request.FILES['photo']:
                      photo = request.FILES['photo']
                      name = request.FILES['photo'].name
                      fs = FileSystemStorage()
                  ##### you can update file saving location too by adding line below #####
                      fs.base_location = fs.base_location+'/company_coverphotos'
                  ##################
                      filename = fs.save(name, photo)
                      uploaded_file_url = fs.url(filename)+'/company_coverphotos'
                      Profile.objects.filter(user=request.user).update(photo=photo)
                  

                  【讨论】:

                  • 谢谢 Nids,绝对可以解决这个问题!你节省了我很多时间:)
                  【解决方案16】:
                  class DemoImage(models.Model):
                      title = models.TextField(max_length=255, blank=False)
                      image = models.ImageField(blank=False, upload_to="images/DemoImages/")
                  
                  import requests
                  import urllib.request
                  from django.core.files import File
                  url = "https://path/to/logo.jpg"
                  
                  # Below 3 lines is to fake as browser agent 
                  # as many sites block urllib class suspecting to be bots
                  opener = urllib.request.build_opener()
                  opener.addheaders = [("User-agent", "Mozilla/5.0")]
                  urllib.request.install_opener(opener)
                  
                  # Issue command to actually download and create temp img file in memory        
                  result = urllib.request.urlretrieve(url)
                  
                  # DemoImage.objects.create(title="title", image=File(open(result[0], "rb"))) 
                  # ^^ This erroneously results in creating the file like 
                  # images/DemoImages/path/to/temp/dir/logo_image_file 
                  # as opposed to 
                  # images/DemoImages/logo_image_file
                  
                  # Solution to get the file in images/DemoImages/
                  reopen = open(result[0], "rb") # Returns a BufferedReader object of the temp image
                  django_file = File(reopen)     # Create the file from the BufferedReader object 
                  demoimg = DemoImage()
                  demoimg.title = "title"
                  demoimg.image.save("logo.png", django_file, save=True)
                  

                  如果这样配置,此方法还会触发文件上传到 cloudinary/S3

                  【讨论】:

                    【解决方案17】:

                    因此,如果您的模型具有带有 upload_to 属性集的图像字段,例如:

                    class Avatar(models.Model):
                        image_file = models.ImageField(upload_to=user_directory_path_avatar)
                    

                    那么更改图像相当容易,至少在 django 3.15 中。

                    在视图中,处理图像时,可以从以下位置获取图像:

                    self.request.FILES['avatar']
                    

                    这是一个 InMemoryUploadedFile 类型的实例,只要你的 html 表单有 enctype 集和头像字段...

                        <form method="post" class="avatarform" id="avatarform" action="{% url avatar_update_view' %}" enctype="multipart/form-data">
                             {% csrf_token %}
                             <input id="avatarUpload" class="d-none" type="file" name="avatar">
                        </form>
                    

                    然后,在视图中设置新图像就像下面这样简单(其中 profile 是 self.request.user 的配置文件模型)

                    profile.avatar.image_file.save(self.request.FILES['avatar'].name, self.request.FILES['avatar'])
                    

                    由于'upload_to'回调函数,无需保存profile.avatar,image_field已经保存,并保存到正确的位置。

                    【讨论】:

                      【解决方案18】:

                      您可以使用 Django REST framework 和 python Requests 库以编程方式将图像保存到 Django ImageField

                      这是一个例子:

                      import requests
                      
                      
                      def upload_image():
                          # PATH TO DJANGO REST API
                          url = "http://127.0.0.1:8080/api/gallery/"
                      
                          # MODEL FIELDS DATA
                          data = {'first_name': "Rajiv", 'last_name': "Sharma"}
                      
                          #  UPLOAD FILES THROUGH REST API
                          photo = open('/path/to/photo', 'rb')
                          resume = open('/path/to/resume', 'rb')
                          files = {'photo': photo, 'resume': resume}
                      
                          request = requests.post(url, data=data, files=files)
                          print(request.status_code, request.reason) 
                      

                      【讨论】:

                        【解决方案19】:

                        我在 django 2 python 3 中使用 uuid 保存图像,因为 django 就是这样做的:

                        import uuid   
                        from django.core.files import File 
                        import urllib
                        
                        httpUrl = "https://miimgeurl/image.jpg"
                        result = urllib.request.urlretrieve(httpUrl)            
                        mymodel.imagefield.save(os.path.basename(str(uuid.uuid4())+".jpg"),File(open(result[0], 'rb')))
                        mymodel.save()
                        

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 2011-12-15
                          • 2013-02-13
                          • 1970-01-01
                          • 2015-03-30
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多