【问题标题】:How to upload files into BinaryField using FileField widget in Django Admin?如何使用 Django Admin 中的 FileField 小部件将文件上传到 BinaryField?
【发布时间】:2018-03-23 09:11:00
【问题描述】:

我想创建一个模型Changelog 并使其可从管理页面进行编辑。这是models.py中的定义:

class Changelog(models.Model):
    id = models.AutoField(primary_key=True, auto_created=True)
    title = models.TextField()
    description = models.TextField()
    link = models.TextField(null=True, blank=True)
    picture = models.BinaryField(null=True, blank=True)

titledescription 是必需的,linkpicture 是可选的。我想让这个模型尽可能简单,所以我选择了BinaryField 而不是FileField。在这种情况下,我不需要担心需要备份的单独文件夹,因为 DB 将是独立的(我不需要存储文件名或任何其他属性,只需存储图像内容) .

我很快意识到,Django Admin 没有BinaryField 的小部件,所以我尝试使用FileField 的小部件。这是我为实现这一目标所做的 (admin.py):

class ChangelogForm(forms.ModelForm):

    picture = forms.FileField(required=False)

    def save(self, commit=True):
        if self.cleaned_data.get('picture') is not None:
            data = self.cleaned_data['picture'].file.read()
            self.instance.picture = data
        return self.instance

    def save_m2m(self):
        # FIXME: this function is required by ModelAdmin, otherwise save process will fail
        pass

    class Meta:
        model = Changelog
        fields = ['title', 'description', 'link', 'picture']


class ChangelogAdmin(admin.ModelAdmin):
    form = ChangelogForm

admin.site.register(Changelog, ChangelogAdmin)

如您所见,它有点老套。您也可以创建自己的表单字段作为子类forms.FileField,但代码几乎相同。它对我来说很好,但现在我在想是否有更好/标准的方法来完成相同的任务?

【问题讨论】:

    标签: python django


    【解决方案1】:

    更好、更标准的方法是为这种类型的字段创建一个Widget

    class BinaryFileInput(forms.ClearableFileInput):
    
        def is_initial(self, value):
            """
            Return whether value is considered to be initial value.
            """
            return bool(value)
    
        def format_value(self, value):
            """Format the size of the value in the db.
    
            We can't render it's name or url, but we'd like to give some information
            as to wether this file is not empty/corrupt.
            """
            if self.is_initial(value):
                return f'{len(value)} bytes'
    
    
        def value_from_datadict(self, data, files, name):
            """Return the file contents so they can be put in the db."""
            upload = super().value_from_datadict(data, files, name)
            if upload:
                return upload.read()
    

    因此,您无需将整个表单子类化,而是在需要的地方使用小部件,例如通过以下方式:

    class MyModelAdmin(admin.ModelAdmin):
        formfield_overrides = {
            models.BinaryField: {'widget': BinaryFileInput()},
        }
    

    正如您已经注意到的那样,代码几乎相同,但这是放置一个字段以特定方式处理的正确位置。实际上,您希望更改一个字段的外观和在表单中使用时的处理方式,而不需要更改整个表单。

    【讨论】:

    • 酷!请告诉我更多信息 - 现在如何将这张图片发送到前端?我将 Django REST 框架与 React 一起使用。使用 JSON 传输二进制数据可以吗?
    • 我想我没有答案。可能需要一个单独的问题一起?
    • @DmitriyVinokurov 您可以阅读我在下面发布的答案
    【解决方案2】:

    另一种解决方案是创建一个file storage backend,它实际上将二进制数据以及文件名和类型保存在数据库中的BinaryField 中。这使您可以保持在FileField 的范式内,并在需要时拥有元数据。

    如果你必须自己做这将是更多的工作,但它已经以db_file_storage的形式完成。

    【讨论】:

      【解决方案3】:

      我实际上遵循了@Ania 的回答并采取了一些解决方法,因为 upload.read() 没有在 Postrgres 中以正确的编码保存图像,并且图像无法在 HTML 模板中呈现。 此外,由于上传字段中的 None 值(更改),重新保存对象将清除二进制字段[这是 Django 仅针对 ImageField 和 FileField 处理的内容] 最后,清除复选框没有正常工作(数据被删除只是因为前一点,即 None in Change)。

      这里我是如何改变 value_from_datadict() 方法来解决的:

      forms.py

      class BinaryFileInput(forms.ClearableFileInput):
          # omitted
      
          def value_from_datadict(self, data, files, name):
          """Return the file contents so they can be put in the db."""
          #print(data)
          if 'image-clear' in data:
              return None
          else:
              upload = super().value_from_datadict(data, files, name)
              if upload:
                  binary_file_data = upload.read()
                  image_data = base64.b64encode(binary_file_data).decode('utf-8')
                  return image_data
              else:
                  if YourObject.objects.filter(pk=data['pk']).exists():
                      return YourObject.objects.get(pk=data['pk']).get_image
                  else:
                      return None
      

      然后我在模型中将该字段定义为 BinaryField() 并使用 @property 检索前端的图像数据:

      models.py

      image = models.BinaryField(verbose_name='Image', blank = True, null = True, editable=True) # editable in admin
      
      @property
      def get_image(self):
          '''
          store the image in Postgres as encoded string 
          then display that image in template using 
          <img src="data:image/<IMAGE_TYPE>;base64,<BASE64_ENCODED_IMAGE>">
          '''
          image_data = base64.b64encode(self.image).decode('utf-8')
          return image_data
      

      最后在模板中渲染:

      yourtemplate.html

      <img src="data:image/jpg;base64,{{object.get_image}}" alt="photo">
      

      【讨论】:

        猜你喜欢
        • 2020-02-20
        • 2021-05-21
        • 1970-01-01
        • 2013-06-01
        • 2011-04-18
        • 1970-01-01
        • 2023-04-11
        • 2018-04-18
        • 1970-01-01
        相关资源
        最近更新 更多