summaryrefslogtreecommitdiff
path: root/django/newforms
diff options
context:
space:
mode:
Diffstat (limited to 'django/newforms')
-rw-r--r--django/newforms/extras/widgets.py2
-rw-r--r--django/newforms/fields.py56
-rw-r--r--django/newforms/forms.py13
-rw-r--r--django/newforms/models.py22
-rw-r--r--django/newforms/widgets.py19
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):
"""