Index: django/forms/models.py
===================================================================
--- django/forms/models.py	(revision 8463)
+++ django/forms/models.py	(working copy)
@@ -6,20 +6,54 @@
 from django.utils.translation import ugettext_lazy as _
 from django.utils.encoding import smart_unicode
 from django.utils.datastructures import SortedDict
+from django.core.files.uploadedfile import UploadedFile
+from django.core.files import directories
 
 from util import ValidationError, ErrorList
 from forms import BaseForm, get_declared_fields
-from fields import Field, ChoiceField, IntegerField, EMPTY_VALUES
+from fields import Field, ChoiceField, IntegerField, FileField, DeletableFileField, EMPTY_VALUES
 from widgets import Select, SelectMultiple, HiddenInput, MultipleHiddenInput
 from widgets import media_property
 from formsets import BaseFormSet, formset_factory, DELETION_FIELD_NAME
 
+import os
+
 __all__ = (
     'ModelForm', 'BaseModelForm', 'model_to_dict', 'fields_for_model',
     'save_instance', 'form_for_fields', 'ModelChoiceField',
     'ModelMultipleChoiceField',
 )
 
+def delete_file_field_file(file_field, model_instance, delete_from_disk=False, delete_empty_directories=False):
+    """
+    Deletes the file of a model instances FileField. But only if no other 
+    instances are pointing to it and it is not the default.
+    """
+    file_obj = getattr(model_instance, file_field.attname)
+    if not file_obj:
+        return
+    
+    # check whether we need to delete the file from disk or just clear the field
+    if delete_from_disk:
+        # create a queryset to determine if the file is being referenced
+        # by *another* instance.
+        queryset = model_instance.__class__._default_manager.\
+            filter(**{file_field.attname: file_obj.name}).\
+            exclude(pk=model_instance.pk)
+        # delete the file if is not referenced elsewhere and is not the default
+        if not queryset and file_obj.name != file_field.default:
+            file_path = file_obj.path
+            file_obj.delete(save=False)
+            if delete_empty_directories:
+                from django.conf import settings
+                directories.delete_empty_directories(stop_dir=settings.MEDIA_ROOT, dir=os.path.dirname(file_path))
+            
+    # TODO: look into this more. initially it was using None, but that
+    # was not working as the model field is null=False since there
+    # seems to be no reason why when the database field is a varchar.
+    setattr(model_instance, file_field.attname, u"")
+
+
 def save_instance(form, instance, fields=None, fail_message='saved',
                   commit=True):
     """
@@ -40,7 +74,23 @@
             continue
         if fields and f.name not in fields:
             continue
-        f.save_form_data(instance, cleaned_data[f.name])
+        cleaned_field_data = cleaned_data[f.name]
+        # if this fields form field is a DeletableFileField instance we may need to delete the file
+        delete_file = False
+        if isinstance(form.fields[f.name], DeletableFileField):
+            # unpack the actual data and the delete checkbox value
+            cleaned_field_data, delete_file = cleaned_field_data
+        # replace file
+        if isinstance(cleaned_field_data, UploadedFile):
+            # TODO: if a user is uploading a file and checked the box that will
+            # call delete_file twice. 1) to delete the current file and 2) to
+            # delete the just uploaded file. maybe make this more clear or have a
+            # better sensible default.
+            delete_file_field_file(f, instance, form._meta.files_delete_from_disk, form._meta.files_delete_empty_dirs)
+        f.save_form_data(instance, cleaned_field_data)
+        if delete_file:
+            delete_file_field_file(f, instance, form._meta.files_delete_from_disk, form._meta.files_delete_empty_dirs)
+            
     # Wrap up the saving of m2m data as a function.
     def save_m2m():
         opts = instance._meta
@@ -150,8 +200,10 @@
         self.model = getattr(options, 'model', None)
         self.fields = getattr(options, 'fields', None)
         self.exclude = getattr(options, 'exclude', None)
+        self.files_add_delete_option = getattr(options, 'files_add_delete_option', True)
+        self.files_delete_from_disk = getattr(options, 'files_delete_from_disk', True)
+        self.files_delete_empty_dirs = getattr(options, 'files_delete_empty_dirs', False)
 
-
 class ModelFormMetaclass(type):
     def __new__(cls, name, bases, attrs):
         formfield_callback = attrs.pop('formfield_callback',
@@ -177,6 +229,12 @@
             # Override default model fields with any custom declared ones
             # (plus, include all the other declared fields).
             fields.update(declared_fields)
+            
+            if opts.files_add_delete_option:
+                # wrap form FileFields in DeletableFileFields to show current file and get delete checkboxes (but only if blank=True)
+                for field_name, field in fields.items():
+                    if isinstance(field, FileField):
+                        fields[field_name] = DeletableFileField(file_field_to_wrap=field, show_delete_checkbox=opts.model._meta.get_field(field_name).blank)
         else:
             fields = declared_fields
         new_class.declared_fields = declared_fields
@@ -219,7 +277,7 @@
     __metaclass__ = ModelFormMetaclass
 
 def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
-                       formfield_callback=lambda f: f.formfield()):
+                       formfield_callback=lambda f: f.formfield(), **kwargs):
     # HACK: we should be able to construct a ModelForm without creating
     # and passing in a temporary inner class
     class Meta:
@@ -227,6 +285,10 @@
     setattr(Meta, 'model', model)
     setattr(Meta, 'fields', fields)
     setattr(Meta, 'exclude', exclude)
+    # only add the options to the Meta class that are actually passed in via kwargs as the defaults are already in ModelFormOptions!
+    for attr in ('files_add_delete_option', 'files_delete_from_disk', 'files_delete_empty_dirs'):
+        if attr in kwargs:
+            setattr(Meta, attr, kwargs[attr])
     class_name = model.__name__ + 'Form'
     return ModelFormMetaclass(class_name, (form,), {'Meta': Meta,
                               'formfield_callback': formfield_callback})
Index: django/forms/fields.py
===================================================================
--- django/forms/fields.py	(revision 8463)
+++ django/forms/fields.py	(working copy)
@@ -27,7 +27,7 @@
 from django.utils.encoding import smart_unicode, smart_str
 
 from util import ErrorList, ValidationError
-from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput
+from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, DateTimeInput, DeletableFileInput, DeleteCheckboxInput
 from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile
 
 __all__ = (
@@ -35,7 +35,7 @@
     'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
     'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
     'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
-    'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField',
+    'RegexField', 'EmailField', 'FileField', 'DeletableFileField', 'ImageField', 'URLField',
     'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField',
     'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
     'SplitDateTimeField', 'IPAddressField', 'FilePathField',
@@ -519,6 +519,29 @@
             f.seek(0)
         return f
 
+class DeletableFileField(Field):
+    """
+    A Field that adds a delete checkbox to a FileField and shows the current value. file_field_to_wrap is the
+    FileField subclass or its instance to be wrapped. If show_delete_checkbox is set to False the field just
+    displays the current value and no delete checkbox. It returns a tupel with the actual FileField value and
+    the state of the delete checkbox.
+    """
+    def __init__(self, file_field_to_wrap=FileField, show_delete_checkbox=True, *args, **kwargs):
+        self.fields = (
+            isinstance(file_field_to_wrap, type) and file_field_to_wrap() or file_field_to_wrap, # instantiate if it is a class
+            BooleanField(widget=DeleteCheckboxInput, required=False),
+        )
+        # get widgets from the fields and pass them to our MultiWidget
+        defaults = {
+            'widget': DeletableFileInput([f.widget for f in self.fields], show_delete_checkbox=show_delete_checkbox),
+            'required': self.fields[0].required, # this is just for display purposes, the actual check is performed by the FileField
+        }
+        defaults.update(kwargs)
+        super(DeletableFileField, self).__init__(*args, **defaults)
+    
+    def clean(self, value, initial=None):
+        return (self.fields[0].clean(value[0], initial), self.fields[1].clean(value[1]))
+
 url_re = re.compile(
     r'^https?://' # http:// or https://
     r'(?:(?:[A-Z0-9-]+\.)+[A-Z]{2,6}|' #domain...
Index: django/forms/forms.py
===================================================================
--- django/forms/forms.py	(revision 8463)
+++ django/forms/forms.py	(working copy)
@@ -9,7 +9,7 @@
 from django.utils.encoding import StrAndUnicode, smart_unicode, force_unicode
 from django.utils.safestring import mark_safe
 
-from fields import Field, FileField
+from fields import Field, FileField, DeletableFileField
 from widgets import Media, media_property, TextInput, Textarea
 from util import flatatt, ErrorDict, ErrorList, ValidationError
 
@@ -211,7 +211,7 @@
             # widgets split data over several HTML fields.
             value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
             try:
-                if isinstance(field, FileField):
+                if isinstance(field, (FileField, DeletableFileField)):
                     initial = self.initial.get(name, field.initial)
                     value = field.clean(value, initial)
                 else:
Index: django/forms/widgets.py
===================================================================
--- django/forms/widgets.py	(revision 8463)
+++ django/forms/widgets.py	(working copy)
@@ -16,13 +16,15 @@
 from django.utils.encoding import StrAndUnicode, force_unicode
 from django.utils.safestring import mark_safe
 from django.utils import datetime_safe
+from django.utils.translation import ugettext as _
+from django.core.files.uploadedfile import UploadedFile
 from util import flatatt
 from urlparse import urljoin
 
 __all__ = (
     'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
-    'HiddenInput', 'MultipleHiddenInput',
-    'FileInput', 'DateTimeInput', 'Textarea', 'CheckboxInput',
+    'HiddenInput', 'MultipleHiddenInput', 'FileInput', 'DateTimeInput',
+    'Textarea', 'CheckboxInput', 'DeletableFileInput', 'DeleteCheckboxInput',
     'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
     'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
 )
@@ -638,6 +640,74 @@
         return media
     media = property(_get_media)
     
+class DeletableFileInput(MultiWidget):
+    """
+    A MultiWidget for the use with the DeletableFileField. The contained widgets are
+    specified by the fields constructor. If the value of the model FileField is None or
+    UploadedFile the rendered widget of the form FileField is just passed through.
+    """
+    def __init__(self, widgets, attrs=None, show_delete_checkbox=True):
+        self.show_delete_checkbox = show_delete_checkbox
+        super(DeletableFileInput, self).__init__(widgets, attrs)
+    
+    def render(self, name, value, attrs=None):
+        self.show_checkbox = False
+        return super(DeletableFileInput, self).render(name, value, attrs)
+        
+    def decompress(self, value):
+        # decompress() is only called when we have initial data (not POST data)
+        # so we can be sure we have a proper File object here
+        if value:
+            self.show_checkbox = True
+            self.current_file = value
+            return [value, False]
+        # the value for DeleteCheckboxInput doesn't really matter as we won't show it anyway
+        return [None, None]
+    
+    def format_output(self, rendered_widgets):
+        """
+        Adds a link to the current file and wraps everything in spans to afford formatting.
+        """
+        # this is a bit strange as we to do the actual test in decompress()
+        # but we don't have the value of the model FileField here so ...
+        if not self.show_checkbox:
+            return rendered_widgets[0]
+        # current file
+        output = [u'<span class="%s"><span class="%s">%s <a target="_blank" href="%s">%s</a></span><br/>' % (
+            u'deletable-file-input',
+            u'deletable-file-input-current',
+            _('Currently:'),
+            self.current_file.url,
+            self.current_file,
+        )]
+        # file input
+        output.append('<span class="%s">%s %s</span><br/>' % (
+            u'deletable-file-input-file-field',
+            _('Change:'),
+            rendered_widgets[0], # the FileFields widget
+        ))
+        # delete checkbox
+        if self.show_delete_checkbox:
+            output.append('<span class="%s">%s</span>' % (
+                u'deletable-file-input-delete-checkbox',
+                rendered_widgets[1], # the checkbox
+            ))
+        output.append('</span>')
+        return u''.join(output)
+
+class DeleteCheckboxInput(CheckboxInput):
+    """
+    A CheckboxInput with a delete label next to it. It only renders if value is not None.
+    """
+    def render(self, name, value, attrs=None):
+        if value == None:
+            return u''
+        return u'%s <label for="%s">%s</label>' % (
+            super(DeleteCheckboxInput, self).render(name, value, attrs),
+            attrs["id"],
+            _("Delete"),
+        )
+
 class SplitDateTimeWidget(MultiWidget):
     """
     A Widget that splits datetime input into two <input type="text"> boxes.
Index: django/core/files/directories.py
===================================================================
--- django/core/files/directories.py	(revision 0)
+++ django/core/files/directories.py	(revision 0)
@@ -0,0 +1,20 @@
+import os
+
+def delete_empty_directories(stop_dir, dir):
+    """
+    Deletes *empty* directories starting with dir and working it's way up till it hits stop_dir.
+    """
+    # normalize paths
+    stop_dir = os.path.abspath(stop_dir)
+    dir = os.path.abspath(dir)
+    if not os.path.exists(stop_dir):
+        raise Exception("stop_dir ('%s') is not a valid directory." % stop_dir)
+    try:
+        while True:
+            if dir == stop_dir or len(dir) < 4:
+                return
+            os.rmdir(dir)
+            dir = os.path.dirname(dir)
+    except OSError:
+        # stop if we have not enough permissions or dir is not empty
+        pass
Index: django/contrib/admin/media/css/forms.css
===================================================================
--- django/contrib/admin/media/css/forms.css	(revision 8463)
+++ django/contrib/admin/media/css/forms.css	(working copy)
@@ -41,6 +41,12 @@
 fieldset.collapsed h2 { background-image:url(../img/admin/nav-bg.gif); background-position:bottom left; color:#999; }
 fieldset.collapsed .collapse-toggle { padding:3px 5px !important; background:transparent; display:inline !important;}
 
+/* DELETABLE FILE INPUTS */
+
+.deletable-file-input { display: block; padding-left: 9.818181em} /* should match the 9em that inputs get pushed to the side by labels */
+.deletable-file-input-delete-checkbox { display: block; }
+.deletable-file-input-delete-checkbox label { display:inline; float:none; }
+
 /* MONOSPACE TEXTAREAS */
 fieldset.monospace textarea { font-family:"Bitstream Vera Sans Mono",Monaco,"Courier New",Courier,monospace; }
 
Index: django/contrib/admin/options.py
===================================================================
--- django/contrib/admin/options.py	(revision 8463)
+++ django/contrib/admin/options.py	(working copy)
@@ -99,11 +99,6 @@
             kwargs['widget'] = widgets.AdminTextInputWidget
             return db_field.formfield(**kwargs)
     
-        # For FileFields and ImageFields add a link to the current file.
-        if isinstance(db_field, models.ImageField) or isinstance(db_field, models.FileField):
-            kwargs['widget'] = widgets.AdminFileWidget
-            return db_field.formfield(**kwargs)
-
         # For ForeignKey or ManyToManyFields, use a special widget.
         if isinstance(db_field, (models.ForeignKey, models.ManyToManyField)):
             if isinstance(db_field, models.ForeignKey) and db_field.name in self.raw_id_fields:
Index: django/contrib/admin/widgets.py
===================================================================
--- django/contrib/admin/widgets.py	(revision 8463)
+++ django/contrib/admin/widgets.py	(working copy)
@@ -75,21 +75,6 @@
 class AdminRadioSelect(forms.RadioSelect):
     renderer = AdminRadioFieldRenderer
 
-class AdminFileWidget(forms.FileInput):
-    """
-    A FileField Widget that shows its current value if it has one.
-    """
-    def __init__(self, attrs={}):
-        super(AdminFileWidget, self).__init__(attrs)
-        
-    def render(self, name, value, attrs=None):
-        output = []
-        if value and hasattr(value, "url"):
-            output.append('%s <a target="_blank" href="%s">%s</a> <br />%s ' % \
-                (_('Currently:'), value.url, value, _('Change:')))
-        output.append(super(AdminFileWidget, self).render(name, value, attrs))
-        return mark_safe(u''.join(output))
-
 class ForeignKeyRawIdWidget(forms.TextInput):
     """
     A Widget for displaying ForeignKeys in the "raw_id" interface rather than
