I want to create a model Changelog
and make it editable from Admin page. Here is how it is defined in 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)
title
and description
are required, link
and picture
are optional. I wanted to keep this model as simple as possible, so I chose BinaryField
over FileField
. In this case I wouldn't need to worry about separate folder I need to backup, because DB will be self-contained (I don't need to store filename or any other attributes, just image content).
I quickly realized, that Django Admin doesn't have a widget for BinaryField
, so I tried to use widget for FileField
. Here is what I did to accomplish that (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)
As you can see it is a bit hacky. You also can create you own form field be subclassing forms.FileField
, but code would be pretty much the same. It is working fine for me, but now I'm thinking is there are better/standard way to accomplish the same task?
A better and more standard way would be to create a Widget
for this type of field.
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()
So instead of subclassing the whole form you would just use the widget where it's needed, e.g. in the following way:
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
models.BinaryField: {'widget': BinaryFileInput()},
}
As you already noticed the code is much the same but this is the right place to put one field to be handled in a specific manner. Effectively you want to change one field's appearance and the way of handling when used in a form, while you don't need to change the whole form.
Update
Since writing that response Django has introduced an editable
field on the models and in order to get this working you need to set the model field to editable=True
which is false for BinaryField
by default.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With