【问题标题】:How to use Django ImageField, and why use it at all?如何使用 Django ImageField,以及为什么要使用它?
【发布时间】:2016-09-17 02:54:19
【问题描述】:

到目前为止,我一直将图像文件名存储在 CharField 中,并将实际文件直接保存到 S3。这是我自己使用的一个很好的解决方案。我想重新考虑使用 ImageField,因为现在会有其他用户并且文件输入验证是合适的。

在阅读 the docsthe source code 的 FileField(这似乎本质上是 ImageField 减去 Pillow 检查和维度字段更新功能)后,我有几个问题没有得到完全回答。

1) 为什么要使用 ImageField?或者更确切地说,为什么要使用 FileField?当然,它对于快速简单的表单和插入到 Django 模板很方便。但是是否有任何实质性的原因,例如。它是否明显受到攻击和恶意上传的保护?

2) 如何写入字段文件?如果instance.imagefield(或者是instance.imagefield.file?)可以读取该文件是正确的,如果我想写入它,我可以简单地执行以下操作吗?

@receiver(pre_save, sender=Image)
def pre_save_image(sender, instance, *args, **kwargs):
    instance.imagefield = process_image(instance.imagefield)

3) 如果随机生成的文件名已经存在,如何尝试使用特定文件名保存,然后使用新文件名重试?例如,我现在使用我的代码执行此操作,如何使用 ImageField 完成?我想在模型层执行此操作,因为如果我在视图层重复尝试,那么pre_save 处理将再次运行,这是贫民窟(即使它不太可能在生命周期内进行第二次尝试)服务)。

for i in range(tries):
    try:
        name = generate_random_name()
        media_storage.save(name + '.jpg', ContentFile(final_bytes))
        break
    except:
        pass

4) 在models.py pre_savepost_save 信号以及实际模型的save() 中,我如何判断是否有文件与请求一起进入?即我想知道是否有新图像要保存,或者是否没有图像(对象中的某些其他字段正在更新并且图像本身保持不变)。

【问题讨论】:

  • 这真的应该是你知道的四个不同的问题。奇怪的是,你没有吸引任何密切的选票!我的建议是让你现在结束这个问题。如果您对特定点 1、2、3、4 的任何答案不满意,请仅针对该点发布另一个问题。这样,您将获得更好、更具体的答案,并提出更具体的问题。
  • 感谢您的建议,您的解决方案会奏效。我还不想结束这个问题,仍然想收到 dkarchmer 的回复:re: uploading directly to S3
  • 那么你从这里去了哪里?

标签: django django-models


【解决方案1】:

我没有看到 FileField 或 ImageField 比您今天所做的任何优势。事实上,在我看来,处理上传的正确/现代/可扩展方式是让客户端(浏览器)直接将文件上传到 S3。

如果操作正确(从安全的角度来看),此方案允许您以令人难以置信的方式进行扩展,而无需在您身边增加更多的计算机能力。例如,考虑 100 个人同时上传一张图片。您的服务器将需要接收所有这些数据,然后再将其上传到 S3。另一方面,您可以同时上传 1000 人,我可以向您保证 AWS 可以处理它。您的服务器只需要处理 URL 的签名,工作量少很多。

看看 Fine-uploader,它是一种很好的技术,可用于处理高效上传到 s3(分块加载、错误检查等):http://docs.fineuploader.com/endpoint_handlers/amazon-s3.html。谷歌“django fineuploader”找到 Django 的示例应用程序。

在我的例子中,我使用了一个带有一对 CharFieldsbucketkey)的模型以及一些其他特定于我的应用程序的东西。我的数据流如下:

  • Django 使用基于我的设置配置的精细上传器小部件为页面提供服务。
  • Fineuploader 从 django 服务器(端点)请求一个签名的 URL,并使用它直接上传到 S3。
  • 上传完成后,fineUploader 向我的服务器发出另一个请求以注册上传完成,此时,我在数据库上创建我的对象。在这种情况下,如果上传失败,我永远不会在数据库上创建对象。
  • 在 AWS 端,S3 触发一个 Lambda 函数,我用它来创建缩略图,并将其存储回 S3。所以,我什至不使用我自己的 CPU(例如 Celery)来调整大小。所以你看,我不仅可以让成千上万的用户同时上传,而且我可以并行调整这几千张图片的大小,而且花费的费用比 EC2 工作人员要少。
  • 我的 Django 模型也被用作管理业务逻辑的包装器(例如 get_original_url()get_thumbnail_url() 等函数),因此在上传后,我的模板很容易获得签名的只读 URL。

简而言之,如果您愿意,您可以实施自己的 Fineuploader 版本,或者使用许多替代方案,但假设您遵循 AWS 方面推荐的安全最佳实践(例如,创建一个仅对客户端,即使您使用签名的 URL),这在 IMO 中是处理上传的最佳实践,尤其是在您使用 S3 或类似工具来存储这些文件时。

抱歉,我只是真正回答问题 1,但如果您接受我对问题 1 的回答,则问题 2 和 3 不适用。

【讨论】:

  • 您能否在创建拇指的 AWS 函数中提供更多信息?只是问一下,因为我目前正在使用 PIL 创建一堆不同的图像大小,然后再将其推送到 S3。顺便说一句,我还将每种尺寸存储在不同的存储桶中。
  • 那里有几个示例和包,但这里是我使用的代码:gist.github.com/dkarchmer/d68e20f6de36827d6c2f0f640bf151e1
  • 我使用 EC2 的原因之一是因为我想使用 mozjpeg 压缩和优化图像(它所做的只是使用 Popen 将图像通过管道传输到 mozjpeg 并返回)。有没有为此目的使用 JS 库,或者如果直接上传到 S3,仍然可以这样做?另外,与 ImageField 相比,仅使用 CharFields 有什么不足吗?
  • 如果我正确理解文档,ImageField 在数据库中使用最大长度为 100 的 varchar。如果是这种情况,那么说它们之间的唯一区别在Django,只要文件上传正确,我什至不需要在 ImageField、FileField 和 CharField 之间来回切换迁移?
  • @david-tan 你几乎可以在 JS 上做任何事情。查看 imagemin-mozjpeg。但 Lambda 现在支持 python,因此您可以使用现有函数。但是,即使您想在自己的 EC2 上执行此操作,最好还是在后台工作程序上执行此操作,而不是在您的前端服务器上执行此操作。您可以配置 S3 以触发您的工作人员可以拉取的 SQS 消息。是的,FileField、ImageField (django-storage) 只是 char 字段顶部的包装器。如果你想更 Pythonic,你可以编写自己的自定义字段
【解决方案2】:

花了我很长时间学习如何使用 ImageField 保存图像。事实证明这非常简单——一旦你知道如何去做,至少它是。我的意思是,在你看到它之后,这一切都明智地结合在一起了。

所以基本上,您使用的是 FileField。我已经研究过 ImageField 和 FileField 之间的区别:

  • ImageField 采用 FileField 采用的所有属性, 但 ImageField 也需要一个宽度和高度属性,如果指示的话。 ImageField 与 FileField 不同,它验证上传,确保它是 图片。

使用 ImageField 可以归结为与 FileField 相同的大部分构造。要记住的最重要的事情:

request.FILES['name_of_model']

因此,表单是从 forms.py(或您的表单所在的任何位置)中的某些内容生成的,如下所示:

imgfile         =   forms.ImageField(label = 'Choose your image',  
                                          help_text = 'The image should be cool.')

在模型中,你可能有这样的对应关系:

imgfile = models.ImageField(upload_to='images/%m/%d') 所以会有来自用户的 POST 请求(当用户完成表单时)。该请求将基本上包含一个数据字典。字典保存提交的文件。要将请求集中在来自字段(在我们的示例中为 ImageField)的文件上,您可以使用:

request.FILES['imgfield']

您将在构建模型对象时使用它(实例化您的模型类):

newPic = ImageModel(imgfile = request.FILES['imgfile'])

要以简单的方式保存它,您只需使用赋予对象的 save() 方法(因为 Django 太棒了):

if form.is_valid():  
        newPic = Pic(imgfile = request.FILES['imgfile'])
        newPic.save()

默认情况下,您的图像将存储到您在 settings.py 中为 MEDIA_ROOT 指定的目录。

最难的部分是访问图像。

在你的模板中,你可以有这样的东西:

<img src="{{ MEDIA_URL }}{{ image.imgfile.name }}"></img>

其中 {{ MEDIA_URL }} 类似于 /media/,如 settings.py 中所示,而 {{ image.imgfile.name }} 是您在模型中指定的文件和子目录的名称。在这种情况下,“图像”只是您可能创建的图像循环中的当前图像,以访问数据库中的每个图像:

{% for image in images %}

  • {% endfor %}

    确保您正确配置您的网址以处理图片,否则图片将无法正常工作。将此添加到您的网址:

    urlpatterns += patterns('',  
            url(r'^media/(?P<path>.*)$', 'django.views.static.serve', {
                'document_root': settings.MEDIA_ROOT,
            }),
       )
    
  • 【讨论】:

    • 这个答案允许直接从 request.FILES 创建 ImageField。
    【解决方案3】:

    1) 为什么要使用 ImageField?或者更确切地说,为什么要使用 FileField?

    方便快捷的表格,方便插入 到 Django 模板。

    但是是否有任何实质性的原因,例如。它是否明显受到攻击和恶意上传的保护?

    是的。我敢说您自己的代码也可能会这样做,但是对于新手来说,使用 FileField 可能会确保您的重要系统文件不会被恶意上传覆盖。

    2) 如何写入字段文件?

    在您的情况下,您需要使用一个特殊的存储后端,以便可以直接写入 Amazon S3。如您所知,FileFile 和 ImageField 的存储后端是可插入的。这是一个示例插件:`http://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html

    有示例代码演示了如何写入。所以我就不多说了。`

    3) 如果随机生成的文件名已经存在,如何尝试使用特定文件名保存,然后使用新文件名重试?

    ImageField 和 FileField 会自动为您解决这个问题。如果旧文件名存在,它将创建一个新文件名。当我一遍又一遍地调用它时,我的答案here 中的代码会自动执行此操作。以下是一些示例文件名生成(输入为 bada.png)

    "4", "media/bada.png"
    "5", "media/bada_aH0gV7t.png"
    "7", "media/bada_XkzthgK.png"
    "8", "media/bada_YzZuwDi.png"
    "9", "media/bada_wpkasI3.png"
    

    4) 在models.py pre_save 和post_save 信号中以及在实际模型的save() 中,我如何判断文件是否与请求一起进入?

    您的instance.pk 将为无

    如果这是对现有文件的修改,则会设置 PK。


    如果这是pre_save中的新图片上传

    【讨论】:

    • 2) 这就是我目前正在使用的,但我的意思是在信号中,尝试更改 instance.image 会起作用吗?这甚至是正确的路径,还是类似于instance.image.file? 3)但如果我没记错的话,S3 默认会覆盖。在我自己的实现中,我只是使用具有只写权限的存储类的另一个实例,但这似乎不适用于 ImageField。 4) 是的,但这是有漏洞的,用新图像更新现有对象会绕过所有处理。
    • 4) 我的意思是,这些东西是通过什么路径或变量或参数进入模型进行保存的?我想检查是否有图像文件,并且只对请求中的图像文件进行操作(而不是任何已经在存储中的图像文件)
    • 2) 我认为无论您是否使用 ImageField,最好的选择是使用哈希文件名。并且为了节省你不应该也不需要使用信号。您应该在视图处理帖子时执行此操作。我相信您使用的是表格?确认表单有效后,将其保存在那里。
    • 4) 如果模型实例尚未保存,则为新上传。就那么简单。之所以会出现这种复杂情况,是因为您正在保存信号。请参考上面的评论。
    • 是的,正是我想说的。然后,您还有机会删除现有文件。
    猜你喜欢
    • 2016-10-26
    • 2012-04-26
    • 2011-10-22
    • 1970-01-01
    • 1970-01-01
    • 2010-10-17
    • 1970-01-01
    • 2020-10-25
    • 2018-05-16
    相关资源
    最近更新 更多