如何使用Django Admin中的FileField小部件将文件上传到BinaryField?

时间:2017-10-11 15:46:22

标签: python django

我想创建一个模型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)

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

2 个答案:

答案 0 :(得分:2)

更好和更标准的方法是为此类型的字段创建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()},
    }

您已经注意到代码非常相似,但这是放置以特定方式处理一个字段的正确位置。实际上,当您不需要更改整个表单时,您实际上想更改一个字段的外观和在表单中使用时的处理方式。

答案 1 :(得分:1)

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

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