Example model:
class MyModel(models.Model):
file = RemovableFileField(upload_to='files', \
null=True, blank=True)
image = RemovableImageField(upload_to='images', \
null=True, blank=True)
A delete checkbox will be automatically rendered when using ModelForm or editing it using form_for_instance. Also, the filename or the image will be displayed below the form field. You can edit the render method of the DeleteCheckboxWidget or add one to RemovableFileFormWidget to customize the rendering.
UPDATE: 5. April 2009. Making it work with latest Django (thanks for the comments).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 | from django import forms
from django.conf import settings
from django.db import models
from django.utils.translation import ugettext as _
import os
class DeleteCheckboxWidget(forms.CheckboxInput):
def __init__(self, *args, **kwargs):
self.is_image = kwargs.pop('is_image')
self.value = kwargs.pop('initial')
super(DeleteCheckboxWidget, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None):
value = value or self.value
if value:
s = u'<label for="%s">%s %s</label>' % (
attrs['id'],
super(DeleteCheckboxWidget, self).render(name, False, attrs),
_('Delete')
)
if self.is_image:
s += u'<br><img src="%s%s" width="50">' % (settings.MEDIA_URL, value)
else:
s += u'<br><a href="%s%s">%s</a>' % (settings.MEDIA_URL, value, os.path.basename(value))
return s
else:
return u''
class RemovableFileFormWidget(forms.MultiWidget):
def __init__(self, is_image=False, initial=None, **kwargs):
widgets = (forms.FileInput(), DeleteCheckboxWidget(is_image=is_image, initial=initial))
super(RemovableFileFormWidget, self).__init__(widgets)
def decompress(self, value):
return [None, value]
class RemovableFileFormField(forms.MultiValueField):
widget = RemovableFileFormWidget
field = forms.FileField
is_image = False
def __init__(self, *args, **kwargs):
fields = [self.field(*args, **kwargs), forms.BooleanField(required=False)]
# Compatibility with form_for_instance
if kwargs.get('initial'):
initial = kwargs['initial']
else:
initial = None
self.widget = self.widget(is_image=self.is_image, initial=initial)
super(RemovableFileFormField, self).__init__(fields, label=kwargs.pop('label'), required=False)
def compress(self, data_list):
return data_list
class RemovableImageFormField(RemovableFileFormField):
field = forms.ImageField
is_image = True
class RemovableFileField(models.FileField):
def delete_file(self, instance, *args, **kwargs):
if getattr(instance, self.attname):
image = getattr(instance, '%s' % self.name)
file_name = image.path
# If the file exists and no other object of this type references it,
# delete it from the filesystem.
if os.path.exists(file_name) and \
not instance.__class__._default_manager.filter(**{'%s__exact' % self.name: getattr(instance, self.attname)}).exclude(pk=instance._get_pk_val()):
os.remove(file_name)
def get_internal_type(self):
return 'FileField'
def save_form_data(self, instance, data):
if data and data[0]: # Replace file
self.delete_file(instance)
super(RemovableFileField, self).save_form_data(instance, data[0])
if data and data[1]: # Delete file
self.delete_file(instance)
setattr(instance, self.name, None)
def formfield(self, **kwargs):
defaults = {'form_class': RemovableFileFormField}
defaults.update(kwargs)
return super(RemovableFileField, self).formfield(**defaults)
class RemovableImageField(models.ImageField, RemovableFileField):
def formfield(self, **kwargs):
defaults = {'form_class': RemovableImageFormField}
defaults.update(kwargs)
return super(RemovableFileField, self).formfield(**defaults)
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 11 months ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months, 1 week ago
- Serializer factory with Django Rest Framework by julio 1 year, 6 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
It sounds like a great snippet, but when I make a model with the following code:
No deletbox shows up in the admin website. Am I forgetting something?
#
This doesn't show up for me as well. Anything else that needs to be done to get it to show up?
#
note: only works with ModelForm and form_for_instance under newforms.
so the admin does not YET work with this. but it will.
#
this snippet is slightly out of date... The "delete_file" in the "RemovableFileField" class needs to be patched that way:
it looks like the rest of the snippets doesn't need to be changed so far. The "args, *kwargs" is needed when you want to use signals, or the model doesn't validate. Using Django 1.0, any File/Image Field comes with a lot of very handy properties, 'path' being very helpful when you want to access the path of a file in the filesystem.
Hope this helps.
#
If there is a verbose name like:
class MyModel(models.Model):
the label returned is file not Hello.
To fix the problem change the line in the class RemovableFileFormField
super(RemovableFileFormField, self).init(fields, required=False)
for
super(RemovableFileFormField, self).init(fields, label=kwargs.pop('label'), required=False)
#
Thanks for the comments. I updated the snippet.
#
Now files are not always represented as strings. Add these lines to DeleteCheckboxWidget:
#
I was trying to use the RemovableFileField in a model and it was causing the whole tabularInline admin section for that model to disappear - whereas RemovableImageField worked fine. I traced the problem to line 26:
changing this to
seemed to fix the problem.
#
Also, if you want the delete checkbox to work when used in an admin inline formset, you'll need to add a _has_changed method to the DeleteCheckboxWidget:
This tells the formset to save if the checkbox is checked. Otherwise your file won't be deleted unless you change another field in the inline formset.
#
Hi.
I can't get this working on current django. I get a stack trace:
if file and not file._committed:
Exception Type: AttributeError at /manager/edit_smartad/1 Exception Value: 'list' object has no attribute '_committed'
#
tezro, to fix the rfind error you must replace "os.path.basename(value)" with "os.path.basename(str(value))"
#
When I check the box to delete a file, but then the form fails validation (for some other field that should be not blank) then the checkbox doesn't keep the value.
#
Hi,
line 80 might cause one user to delete another users' files. Therefore, I would strongly recommend to delete line 80 and just don't delete files.
It occurs when an image is saved with the same name as an existing image: the existing image is removed and replaced by the new image. If the existing image was uploaded by another user it gets replaced by the new image!
The same would happen with files, so potentially a user could get access to another one's file! And his original would be destroyed.
Django normally detects this automatically and adds underscores (_) at the end of a file name to avoid duplication. But in the above snippet, the original image is destroyed first and then there is no underscore added resulting in a new image having the same name as the destroyed file.
#
Please login first before commenting.