mirror of
https://github.com/django/django.git
synced 2025-10-24 14:16:09 +00:00
Fixed #7048 -- Added ClearableFileInput widget to clear file fields. Thanks for report and patch, jarrow and Carl Meyer.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@13968 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
@@ -27,8 +27,9 @@ from django.core.validators import EMPTY_VALUES
|
||||
|
||||
from util import ErrorList
|
||||
from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, \
|
||||
FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, \
|
||||
DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget
|
||||
ClearableFileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple, \
|
||||
DateInput, DateTimeInput, TimeInput, SplitDateTimeWidget, SplitHiddenDateTimeWidget, \
|
||||
FILE_INPUT_CONTRADICTION
|
||||
|
||||
__all__ = (
|
||||
'Field', 'CharField', 'IntegerField',
|
||||
@@ -108,6 +109,9 @@ class Field(object):
|
||||
if self.localize:
|
||||
widget.is_localized = True
|
||||
|
||||
# Let the widget know whether it should display as required.
|
||||
widget.is_required = self.required
|
||||
|
||||
# Hook into self.widget_attrs() for any Field-specific HTML attributes.
|
||||
extra_attrs = self.widget_attrs(widget)
|
||||
if extra_attrs:
|
||||
@@ -167,6 +171,17 @@ class Field(object):
|
||||
self.run_validators(value)
|
||||
return value
|
||||
|
||||
def bound_data(self, data, initial):
|
||||
"""
|
||||
Return the value that should be shown for this field on render of a
|
||||
bound form, given the submitted POST data for the field and the initial
|
||||
data, if any.
|
||||
|
||||
For most fields, this will simply be data; FileFields need to handle it
|
||||
a bit differently.
|
||||
"""
|
||||
return data
|
||||
|
||||
def widget_attrs(self, widget):
|
||||
"""
|
||||
Given a Widget instance (*not* a Widget class), returns a dictionary of
|
||||
@@ -434,12 +449,13 @@ class EmailField(CharField):
|
||||
default_validators = [validators.validate_email]
|
||||
|
||||
class FileField(Field):
|
||||
widget = FileInput
|
||||
widget = ClearableFileInput
|
||||
default_error_messages = {
|
||||
'invalid': _(u"No file was submitted. Check the encoding type on the form."),
|
||||
'missing': _(u"No file was submitted."),
|
||||
'empty': _(u"The submitted file is empty."),
|
||||
'max_length': _(u'Ensure this filename has at most %(max)d characters (it has %(length)d).'),
|
||||
'contradiction': _(u'Please either submit a file or check the clear checkbox, not both.')
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -468,10 +484,29 @@ class FileField(Field):
|
||||
return data
|
||||
|
||||
def clean(self, data, initial=None):
|
||||
# If the widget got contradictory inputs, we raise a validation error
|
||||
if data is FILE_INPUT_CONTRADICTION:
|
||||
raise ValidationError(self.error_messages['contradiction'])
|
||||
# False means the field value should be cleared; further validation is
|
||||
# not needed.
|
||||
if data is False:
|
||||
if not self.required:
|
||||
return False
|
||||
# If the field is required, clearing is not possible (the widget
|
||||
# shouldn't return False data in that case anyway). False is not
|
||||
# in validators.EMPTY_VALUES; if a False value makes it this far
|
||||
# it should be validated from here on out as None (so it will be
|
||||
# caught by the required check).
|
||||
data = None
|
||||
if not data and initial:
|
||||
return initial
|
||||
return super(FileField, self).clean(data)
|
||||
|
||||
def bound_data(self, data, initial):
|
||||
if data in (None, FILE_INPUT_CONTRADICTION):
|
||||
return initial
|
||||
return data
|
||||
|
||||
class ImageField(FileField):
|
||||
default_error_messages = {
|
||||
'invalid_image': _(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image."),
|
||||
|
||||
@@ -437,10 +437,8 @@ class BoundField(StrAndUnicode):
|
||||
if callable(data):
|
||||
data = data()
|
||||
else:
|
||||
if isinstance(self.field, FileField) and self.data is None:
|
||||
data = self.form.initial.get(self.name, self.field.initial)
|
||||
else:
|
||||
data = self.data
|
||||
data = self.field.bound_data(
|
||||
self.data, self.form.initial.get(self.name, self.field.initial))
|
||||
data = self.field.prepare_value(data)
|
||||
|
||||
if not only_initial:
|
||||
|
||||
@@ -7,7 +7,7 @@ from itertools import chain
|
||||
from django.conf import settings
|
||||
from django.utils.datastructures import MultiValueDict, MergeDict
|
||||
from django.utils.html import escape, conditional_escape
|
||||
from django.utils.translation import ugettext
|
||||
from django.utils.translation import ugettext, ugettext_lazy
|
||||
from django.utils.encoding import StrAndUnicode, force_unicode
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils import datetime_safe, formats
|
||||
@@ -18,7 +18,7 @@ from urlparse import urljoin
|
||||
|
||||
__all__ = (
|
||||
'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
|
||||
'HiddenInput', 'MultipleHiddenInput',
|
||||
'HiddenInput', 'MultipleHiddenInput', 'ClearableFileInput',
|
||||
'FileInput', 'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
|
||||
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
|
||||
'CheckboxSelectMultiple', 'MultiWidget',
|
||||
@@ -134,6 +134,7 @@ class Widget(object):
|
||||
is_hidden = False # Determines whether this corresponds to an <input type="hidden">.
|
||||
needs_multipart_form = False # Determines does this widget need multipart-encrypted form
|
||||
is_localized = False
|
||||
is_required = False
|
||||
|
||||
def __init__(self, attrs=None):
|
||||
if attrs is not None:
|
||||
@@ -286,6 +287,67 @@ class FileInput(Input):
|
||||
return False
|
||||
return True
|
||||
|
||||
FILE_INPUT_CONTRADICTION = object()
|
||||
|
||||
class ClearableFileInput(FileInput):
|
||||
initial_text = ugettext_lazy('Currently')
|
||||
input_text = ugettext_lazy('Change')
|
||||
clear_checkbox_label = ugettext_lazy('Clear')
|
||||
|
||||
template_with_initial = u'%(initial_text)s: %(initial)s %(clear_template)s<br />%(input_text)s: %(input)s'
|
||||
|
||||
template_with_clear = u'%(clear)s <label for="%(clear_checkbox_id)s">%(clear_checkbox_label)s</label>'
|
||||
|
||||
def clear_checkbox_name(self, name):
|
||||
"""
|
||||
Given the name of the file input, return the name of the clear checkbox
|
||||
input.
|
||||
"""
|
||||
return name + '-clear'
|
||||
|
||||
def clear_checkbox_id(self, name):
|
||||
"""
|
||||
Given the name of the clear checkbox input, return the HTML id for it.
|
||||
"""
|
||||
return name + '_id'
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
substitutions = {
|
||||
'initial_text': self.initial_text,
|
||||
'input_text': self.input_text,
|
||||
'clear_template': '',
|
||||
'clear_checkbox_label': self.clear_checkbox_label,
|
||||
}
|
||||
template = u'%(input)s'
|
||||
substitutions['input'] = super(ClearableFileInput, self).render(name, value, attrs)
|
||||
|
||||
if value and hasattr(value, "url"):
|
||||
template = self.template_with_initial
|
||||
substitutions['initial'] = (u'<a target="_blank" href="%s">%s</a>'
|
||||
% (value.url, value))
|
||||
if not self.is_required:
|
||||
checkbox_name = self.clear_checkbox_name(name)
|
||||
checkbox_id = self.clear_checkbox_id(checkbox_name)
|
||||
substitutions['clear_checkbox_name'] = checkbox_name
|
||||
substitutions['clear_checkbox_id'] = checkbox_id
|
||||
substitutions['clear'] = CheckboxInput().render(checkbox_name, False, attrs={'id': checkbox_id})
|
||||
substitutions['clear_template'] = self.template_with_clear % substitutions
|
||||
|
||||
return mark_safe(template % substitutions)
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
upload = super(ClearableFileInput, self).value_from_datadict(data, files, name)
|
||||
if not self.is_required and CheckboxInput().value_from_datadict(
|
||||
data, files, self.clear_checkbox_name(name)):
|
||||
if upload:
|
||||
# If the user contradicts themselves (uploads a new file AND
|
||||
# checks the "clear" checkbox), we return a unique marker
|
||||
# object that FileField will turn into a ValidationError.
|
||||
return FILE_INPUT_CONTRADICTION
|
||||
# False signals to clear any existing value, as opposed to just None
|
||||
return False
|
||||
return upload
|
||||
|
||||
class Textarea(Widget):
|
||||
def __init__(self, attrs=None):
|
||||
# The 'rows' and 'cols' attributes are required for HTML correctness.
|
||||
|
||||
Reference in New Issue
Block a user