diff options
Diffstat (limited to 'django/newforms')
| -rw-r--r-- | django/newforms/extras/widgets.py | 2 | ||||
| -rw-r--r-- | django/newforms/fields.py | 56 | ||||
| -rw-r--r-- | django/newforms/forms.py | 13 | ||||
| -rw-r--r-- | django/newforms/models.py | 22 | ||||
| -rw-r--r-- | django/newforms/widgets.py | 19 |
5 files changed, 88 insertions, 24 deletions
diff --git a/django/newforms/extras/widgets.py b/django/newforms/extras/widgets.py index 724dcd9b50..96b1c7244d 100644 --- a/django/newforms/extras/widgets.py +++ b/django/newforms/extras/widgets.py @@ -53,7 +53,7 @@ class SelectDateWidget(Widget): return u'\n'.join(output) - def value_from_datadict(self, data, name): + def value_from_datadict(self, data, files, name): y, m, d = data.get(self.year_field % name), data.get(self.month_field % name), data.get(self.day_field % name) if y and m and d: return '%s-%s-%s' % (y, m, d) diff --git a/django/newforms/fields.py b/django/newforms/fields.py index 58b46ce2b8..e9f50ad4ec 100644 --- a/django/newforms/fields.py +++ b/django/newforms/fields.py @@ -7,10 +7,10 @@ import re import time from django.utils.translation import ugettext -from django.utils.encoding import smart_unicode +from django.utils.encoding import StrAndUnicode, smart_unicode from util import ErrorList, ValidationError -from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple +from widgets import TextInput, PasswordInput, HiddenInput, MultipleHiddenInput, FileInput, CheckboxInput, Select, NullBooleanSelect, SelectMultiple try: from decimal import Decimal, DecimalException @@ -22,7 +22,7 @@ __all__ = ( 'DEFAULT_DATE_INPUT_FORMATS', 'DateField', 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', - 'RegexField', 'EmailField', 'URLField', 'BooleanField', + 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField', 'ChoiceField', 'NullBooleanField', 'MultipleChoiceField', 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', 'SplitDateTimeField', @@ -120,6 +120,7 @@ class CharField(Field): def widget_attrs(self, widget): if self.max_length is not None and isinstance(widget, (TextInput, PasswordInput)): + # The HTML attribute is maxlength, not max_length. return {'maxlength': str(self.max_length)} class IntegerField(Field): @@ -347,6 +348,55 @@ except ImportError: # It's OK if Django settings aren't configured. URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' +class UploadedFile(StrAndUnicode): + "A wrapper for files uploaded in a FileField" + def __init__(self, filename, content): + self.filename = filename + self.content = content + + def __unicode__(self): + """ + The unicode representation is the filename, so that the pre-database-insertion + logic can use UploadedFile objects + """ + return self.filename + +class FileField(Field): + widget = FileInput + def __init__(self, *args, **kwargs): + super(FileField, self).__init__(*args, **kwargs) + + def clean(self, data): + super(FileField, self).clean(data) + if not self.required and data in EMPTY_VALUES: + return None + try: + f = UploadedFile(data['filename'], data['content']) + except TypeError: + raise ValidationError(ugettext(u"No file was submitted. Check the encoding type on the form.")) + except KeyError: + raise ValidationError(ugettext(u"No file was submitted.")) + if not f.content: + raise ValidationError(ugettext(u"The submitted file is empty.")) + return f + +class ImageField(FileField): + def clean(self, data): + """ + Checks that the file-upload field data contains a valid image (GIF, JPG, + PNG, possibly others -- whatever the Python Imaging Library supports). + """ + f = super(ImageField, self).clean(data) + if f is None: + return None + from PIL import Image + from cStringIO import StringIO + try: + Image.open(StringIO(f.content)) + except IOError: # Python Imaging Library doesn't recognize it as an image + raise ValidationError(ugettext(u"Upload a valid image. The file you uploaded was either not an image or a corrupted image.")) + return f + class URLField(RegexField): def __init__(self, max_length=None, min_length=None, verify_exists=False, validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): diff --git a/django/newforms/forms.py b/django/newforms/forms.py index 5da85a69c4..906978c86f 100644 --- a/django/newforms/forms.py +++ b/django/newforms/forms.py @@ -57,9 +57,10 @@ class BaseForm(StrAndUnicode): # class is different than Form. See the comments by the Form class for more # information. Any improvements to the form API should be made to *this* # class, not to the Form class. - def __init__(self, data=None, auto_id='id_%s', prefix=None, initial=None): - self.is_bound = data is not None + def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None): + self.is_bound = data is not None or files is not None self.data = data or {} + self.files = files or {} self.auto_id = auto_id self.prefix = prefix self.initial = initial or {} @@ -88,7 +89,7 @@ class BaseForm(StrAndUnicode): return BoundField(self, field, name) def _get_errors(self): - "Returns an ErrorDict for self.data" + "Returns an ErrorDict for the data provided for the form" if self._errors is None: self.full_clean() return self._errors @@ -179,10 +180,10 @@ class BaseForm(StrAndUnicode): return self.cleaned_data = {} for name, field in self.fields.items(): - # value_from_datadict() gets the data from the dictionary. + # value_from_datadict() gets the data from the data dictionaries. # Each widget type knows how to retrieve its own data, because some # widgets split data over several HTML fields. - value = field.widget.value_from_datadict(self.data, self.add_prefix(name)) + value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) try: value = field.clean(value) self.cleaned_data[name] = value @@ -283,7 +284,7 @@ class BoundField(StrAndUnicode): """ Returns the data for this BoundField, or None if it wasn't given. """ - return self.field.widget.value_from_datadict(self.form.data, self.html_name) + return self.field.widget.value_from_datadict(self.form.data, self.form.files, self.html_name) data = property(_data) def label_tag(self, contents=None, attrs=None): diff --git a/django/newforms/models.py b/django/newforms/models.py index 56a08bc58e..247a0eea6b 100644 --- a/django/newforms/models.py +++ b/django/newforms/models.py @@ -34,18 +34,24 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True continue if fields and f.name not in fields: continue - setattr(instance, f.name, cleaned_data[f.name]) - if commit: - instance.save() + f.save_form_data(instance, cleaned_data[f.name]) + # Wrap up the saving of m2m data as a function + def save_m2m(): + opts = instance.__class__._meta + cleaned_data = form.cleaned_data for f in opts.many_to_many: if fields and f.name not in fields: continue if f.name in cleaned_data: - setattr(instance, f.attname, cleaned_data[f.name]) - # GOTCHA: If many-to-many data is given and commit=False, the many-to-many - # data will be lost. This happens because a many-to-many options cannot be - # set on an object until after it's saved. Maybe we should raise an - # exception in that case. + f.save_form_data(instance, cleaned_data[f.name]) + if commit: + # If we are committing, save the instance and the m2m data immediately + instance.save() + save_m2m() + else: + # We're not committing. Add a method to the form to allow deferred + # saving of m2m data + form.save_m2m = save_m2m return instance def make_model_save(model, fields, fail_message): diff --git a/django/newforms/widgets.py b/django/newforms/widgets.py index e9b9b55470..f985124389 100644 --- a/django/newforms/widgets.py +++ b/django/newforms/widgets.py @@ -47,7 +47,7 @@ class Widget(object): attrs.update(extra_attrs) return attrs - def value_from_datadict(self, data, name): + def value_from_datadict(self, data, files, name): """ Given a dictionary of data and this widget's name, returns the value of this widget. Returns None if it's not provided. @@ -113,7 +113,7 @@ class MultipleHiddenInput(HiddenInput): final_attrs = self.build_attrs(attrs, type=self.input_type, name=name) return u'\n'.join([(u'<input%s />' % flatatt(dict(value=force_unicode(v), **final_attrs))) for v in value]) - def value_from_datadict(self, data, name): + def value_from_datadict(self, data, files, name): if isinstance(data, MultiValueDict): return data.getlist(name) return data.get(name, None) @@ -121,6 +121,13 @@ class MultipleHiddenInput(HiddenInput): class FileInput(Input): input_type = 'file' + def render(self, name, value, attrs=None): + return super(FileInput, self).render(name, None, attrs=attrs) + + def value_from_datadict(self, data, files, name): + "File widgets take data from FILES, not POST" + return files.get(name, None) + class Textarea(Widget): def __init__(self, attrs=None): # The 'rows' and 'cols' attributes are required for HTML correctness. @@ -188,7 +195,7 @@ class NullBooleanSelect(Select): value = u'1' return super(NullBooleanSelect, self).render(name, value, attrs, choices) - def value_from_datadict(self, data, name): + def value_from_datadict(self, data, files, name): value = data.get(name, None) return {u'2': True, u'3': False, True: True, False: False}.get(value, None) @@ -210,7 +217,7 @@ class SelectMultiple(Widget): output.append(u'</select>') return u'\n'.join(output) - def value_from_datadict(self, data, name): + def value_from_datadict(self, data, files, name): if isinstance(data, MultiValueDict): return data.getlist(name) return data.get(name, None) @@ -377,8 +384,8 @@ class MultiWidget(Widget): return id_ id_for_label = classmethod(id_for_label) - def value_from_datadict(self, data, name): - return [widget.value_from_datadict(data, name + '_%s' % i) for i, widget in enumerate(self.widgets)] + def value_from_datadict(self, data, files, name): + return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)] def format_output(self, rendered_widgets): """ |
