Fixed #3297 -- Implemented FileField and ImageField for newforms. Thanks to the many users that contributed to and tested this patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@5819 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2007-08-06 13:58:56 +00:00
parent e471f42ba1
commit fbd1a6277e
9 changed files with 266 additions and 27 deletions

View File

@ -380,6 +380,9 @@ class Field(object):
return self._choices return self._choices
choices = property(_get_choices) choices = property(_get_choices)
def save_form_data(self, instance, data):
setattr(instance, self.name, data)
def formfield(self, form_class=forms.CharField, **kwargs): def formfield(self, form_class=forms.CharField, **kwargs):
"Returns a django.newforms.Field instance for this database Field." "Returns a django.newforms.Field instance for this database Field."
defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text}
@ -696,6 +699,13 @@ class FileField(Field):
self.upload_to = upload_to self.upload_to = upload_to
Field.__init__(self, verbose_name, name, **kwargs) Field.__init__(self, verbose_name, name, **kwargs)
def get_db_prep_save(self, value):
"Returns field's value prepared for saving into a database."
# Need to convert UploadedFile objects provided via a form to unicode for database insertion
if value is None:
return None
return unicode(value)
def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow) field_list = Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel, follow)
if not self.blank: if not self.blank:
@ -772,6 +782,19 @@ class FileField(Field):
f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename))) f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))
return os.path.normpath(f) return os.path.normpath(f)
def save_form_data(self, instance, data):
if data:
getattr(instance, "save_%s_file" % self.name)(os.path.join(self.upload_to, data.filename), data.content, save=False)
def formfield(self, **kwargs):
defaults = {'form_class': forms.FileField}
# If a file has been provided previously, then the form doesn't require
# that a new file is provided this time.
if 'initial' in kwargs:
defaults['required'] = False
defaults.update(kwargs)
return super(FileField, self).formfield(**defaults)
class FilePathField(Field): class FilePathField(Field):
def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
self.path, self.match, self.recursive = path, match, recursive self.path, self.match, self.recursive = path, match, recursive
@ -820,6 +843,10 @@ class ImageField(FileField):
setattr(new_object, self.height_field, getattr(original_object, self.height_field)) setattr(new_object, self.height_field, getattr(original_object, self.height_field))
new_object.save() new_object.save()
def formfield(self, **kwargs):
defaults = {'form_class': forms.ImageField}
return super(ImageField, self).formfield(**defaults)
class IntegerField(Field): class IntegerField(Field):
empty_strings_allowed = False empty_strings_allowed = False
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):

View File

@ -756,6 +756,9 @@ class ManyToManyField(RelatedField, Field):
"Returns the value of this field in the given model instance." "Returns the value of this field in the given model instance."
return getattr(obj, self.attname).all() return getattr(obj, self.attname).all()
def save_form_data(self, instance, data):
setattr(instance, self.attname, data)
def formfield(self, **kwargs): def formfield(self, **kwargs):
defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()} defaults = {'form_class': forms.ModelMultipleChoiceField, 'queryset': self.rel.to._default_manager.all()}
defaults.update(kwargs) defaults.update(kwargs)

View File

@ -53,7 +53,7 @@ class SelectDateWidget(Widget):
return u'\n'.join(output) 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) 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: if y and m and d:
return '%s-%s-%s' % (y, m, d) return '%s-%s-%s' % (y, m, d)

View File

@ -7,10 +7,10 @@ import re
import time import time
from django.utils.translation import ugettext 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 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: try:
from decimal import Decimal, DecimalException from decimal import Decimal, DecimalException
@ -22,7 +22,7 @@ __all__ = (
'DEFAULT_DATE_INPUT_FORMATS', 'DateField', 'DEFAULT_DATE_INPUT_FORMATS', 'DateField',
'DEFAULT_TIME_INPUT_FORMATS', 'TimeField', 'DEFAULT_TIME_INPUT_FORMATS', 'TimeField',
'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField', 'DEFAULT_DATETIME_INPUT_FORMATS', 'DateTimeField',
'RegexField', 'EmailField', 'URLField', 'BooleanField', 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField',
'ChoiceField', 'NullBooleanField', 'MultipleChoiceField', 'ChoiceField', 'NullBooleanField', 'MultipleChoiceField',
'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField',
'SplitDateTimeField', 'SplitDateTimeField',
@ -348,6 +348,55 @@ except ImportError:
# It's OK if Django settings aren't configured. # It's OK if Django settings aren't configured.
URL_VALIDATOR_USER_AGENT = 'Django (http://www.djangoproject.com/)' 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): class URLField(RegexField):
def __init__(self, max_length=None, min_length=None, verify_exists=False, def __init__(self, max_length=None, min_length=None, verify_exists=False,
validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs): validator_user_agent=URL_VALIDATOR_USER_AGENT, *args, **kwargs):

View File

@ -57,9 +57,10 @@ class BaseForm(StrAndUnicode):
# class is different than Form. See the comments by the Form class for more # 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* # information. Any improvements to the form API should be made to *this*
# class, not to the Form class. # class, not to the Form class.
def __init__(self, data=None, auto_id='id_%s', prefix=None, initial=None): def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None):
self.is_bound = data is not None self.is_bound = data is not None or files is not None
self.data = data or {} self.data = data or {}
self.files = files or {}
self.auto_id = auto_id self.auto_id = auto_id
self.prefix = prefix self.prefix = prefix
self.initial = initial or {} self.initial = initial or {}
@ -88,7 +89,7 @@ class BaseForm(StrAndUnicode):
return BoundField(self, field, name) return BoundField(self, field, name)
def _get_errors(self): 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: if self._errors is None:
self.full_clean() self.full_clean()
return self._errors return self._errors
@ -179,10 +180,10 @@ class BaseForm(StrAndUnicode):
return return
self.cleaned_data = {} self.cleaned_data = {}
for name, field in self.fields.items(): 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 # Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields. # 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: try:
value = field.clean(value) value = field.clean(value)
self.cleaned_data[name] = 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. 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) data = property(_data)
def label_tag(self, contents=None, attrs=None): def label_tag(self, contents=None, attrs=None):

View File

@ -34,7 +34,7 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True
continue continue
if fields and f.name not in fields: if fields and f.name not in fields:
continue continue
setattr(instance, f.name, cleaned_data[f.name]) f.save_form_data(instance, cleaned_data[f.name])
# Wrap up the saving of m2m data as a function # Wrap up the saving of m2m data as a function
def save_m2m(): def save_m2m():
opts = instance.__class__._meta opts = instance.__class__._meta
@ -43,7 +43,7 @@ def save_instance(form, instance, fields=None, fail_message='saved', commit=True
if fields and f.name not in fields: if fields and f.name not in fields:
continue continue
if f.name in cleaned_data: if f.name in cleaned_data:
setattr(instance, f.attname, cleaned_data[f.name]) f.save_form_data(instance, cleaned_data[f.name])
if commit: if commit:
# If we are committing, save the instance and the m2m data immediately # If we are committing, save the instance and the m2m data immediately
instance.save() instance.save()

View File

@ -47,7 +47,7 @@ class Widget(object):
attrs.update(extra_attrs) attrs.update(extra_attrs)
return 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 Given a dictionary of data and this widget's name, returns the value
of this widget. Returns None if it's not provided. 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) 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]) 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): if isinstance(data, MultiValueDict):
return data.getlist(name) return data.getlist(name)
return data.get(name, None) return data.get(name, None)
@ -121,6 +121,13 @@ class MultipleHiddenInput(HiddenInput):
class FileInput(Input): class FileInput(Input):
input_type = 'file' 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): class Textarea(Widget):
def __init__(self, attrs=None): def __init__(self, attrs=None):
# The 'rows' and 'cols' attributes are required for HTML correctness. # The 'rows' and 'cols' attributes are required for HTML correctness.
@ -188,7 +195,7 @@ class NullBooleanSelect(Select):
value = u'1' value = u'1'
return super(NullBooleanSelect, self).render(name, value, attrs, choices) 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) value = data.get(name, None)
return {u'2': True, u'3': False, True: True, False: False}.get(value, 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>') output.append(u'</select>')
return u'\n'.join(output) return u'\n'.join(output)
def value_from_datadict(self, data, name): def value_from_datadict(self, data, files, name):
if isinstance(data, MultiValueDict): if isinstance(data, MultiValueDict):
return data.getlist(name) return data.getlist(name)
return data.get(name, None) return data.get(name, None)
@ -377,8 +384,8 @@ class MultiWidget(Widget):
return id_ return id_
id_for_label = classmethod(id_for_label) id_for_label = classmethod(id_for_label)
def value_from_datadict(self, data, name): def value_from_datadict(self, data, files, name):
return [widget.value_from_datadict(data, name + '_%s' % i) for i, widget in enumerate(self.widgets)] return [widget.value_from_datadict(data, files, name + '_%s' % i) for i, widget in enumerate(self.widgets)]
def format_output(self, rendered_widgets): def format_output(self, rendered_widgets):
""" """

View File

@ -710,6 +710,47 @@ For example::
</ul> </ul>
</form> </form>
Binding uploaded files to a form
--------------------------------
Dealing with forms that have ``FileField`` and ``ImageField`` fields
is a little more complicated than a normal form.
Firstly, in order to upload files, you'll need to make sure that your
``<form>`` element correctly defines the ``enctype`` as
``"multipart/form-data"``::
<form enctype="multipart/form-data" method="post" action="/foo/">
Secondly, when you use the form, you need to bind the file data. File
data is handled separately to normal form data, so when your form
contains a ``FileField`` and ``ImageField``, you will need to specify
a second argument when you bind your form. So if we extend our
ContactForm to include an ``ImageField`` called ``mugshot``, we
need to bind the file data containing the mugshot image::
# Bound form with an image field
>>> data = {'subject': 'hello',
... 'message': 'Hi there',
... 'sender': 'foo@example.com',
... 'cc_myself': True}
>>> file_data = {'mugshot': {'filename':'face.jpg'
... 'content': <file data>}}
>>> f = ContactFormWithMugshot(data, file_data)
In practice, you will usually specify ``request.FILES`` as the source
of file data (just like you use ``request.POST`` as the source of
form data)::
# Bound form with an image field, data from the request
>>> f = ContactFormWithMugshot(request.POST, request.FILES)
Constructing an unbound form is the same as always -- just omit both
form data *and* file data:
# Unbound form with a image field
>>> f = ContactFormWithMugshot()
Subclassing forms Subclassing forms
----------------- -----------------
@ -1099,6 +1140,50 @@ Has two optional arguments for validation, ``max_length`` and ``min_length``.
If provided, these arguments ensure that the string is at most or at least the If provided, these arguments ensure that the string is at most or at least the
given length. given length.
``FileField``
~~~~~~~~~~~~~
* Default widget: ``FileInput``
* Empty value: ``None``
* Normalizes to: An ``UploadedFile`` object that wraps the file content
and file name into a single object.
* Validates that non-empty file data has been bound to the form.
An ``UploadedFile`` object has two attributes:
====================== =====================================================
Argument Description
====================== =====================================================
``filename`` The name of the file, provided by the uploading
client.
``content`` The array of bytes comprising the file content.
====================== =====================================================
The string representation of an ``UploadedFile`` is the same as the filename
attribute.
When you use a ``FileField`` on a form, you must also remember to
`bind the file data to the form`_.
.. _`bind the file data to the form`: `Binding uploaded files to a form`_
``ImageField``
~~~~~~~~~~~~~~
* Default widget: ``FileInput``
* Empty value: ``None``
* Normalizes to: An ``UploadedFile`` object that wraps the file content
and file name into a single object.
* Validates that file data has been bound to the form, and that the
file is of an image format understood by PIL.
Using an ImageField requires that the `Python Imaging Library`_ is installed.
When you use a ``FileField`` on a form, you must also remember to
`bind the file data to the form`_.
.. _Python Imaging Library: http://www.pythonware.com/products/pil/
``IntegerField`` ``IntegerField``
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@ -1378,11 +1463,11 @@ the full list of conversions:
``DateTimeField`` ``DateTimeField`` ``DateTimeField`` ``DateTimeField``
``DecimalField`` ``DecimalField`` ``DecimalField`` ``DecimalField``
``EmailField`` ``EmailField`` ``EmailField`` ``EmailField``
``FileField`` ``CharField`` ``FileField`` ``FileField``
``FilePathField`` ``CharField`` ``FilePathField`` ``CharField``
``FloatField`` ``FloatField`` ``FloatField`` ``FloatField``
``ForeignKey`` ``ModelChoiceField`` (see below) ``ForeignKey`` ``ModelChoiceField`` (see below)
``ImageField`` ``CharField`` ``ImageField`` ``ImageField``
``IntegerField`` ``IntegerField`` ``IntegerField`` ``IntegerField``
``IPAddressField`` ``CharField`` ``IPAddressField`` ``CharField``
``ManyToManyField`` ``ModelMultipleChoiceField`` (see ``ManyToManyField`` ``ModelMultipleChoiceField`` (see

View File

@ -173,27 +173,29 @@ u'<input type="hidden" class="special" value="foo@example.com" name="email" />'
# FileInput Widget ############################################################ # FileInput Widget ############################################################
FileInput widgets don't ever show the value, because the old value is of no use
if you are updating the form or if the provided file generated an error.
>>> w = FileInput() >>> w = FileInput()
>>> w.render('email', '') >>> w.render('email', '')
u'<input type="file" name="email" />' u'<input type="file" name="email" />'
>>> w.render('email', None) >>> w.render('email', None)
u'<input type="file" name="email" />' u'<input type="file" name="email" />'
>>> w.render('email', 'test@example.com') >>> w.render('email', 'test@example.com')
u'<input type="file" name="email" value="test@example.com" />' u'<input type="file" name="email" />'
>>> w.render('email', 'some "quoted" & ampersanded value') >>> w.render('email', 'some "quoted" & ampersanded value')
u'<input type="file" name="email" value="some &quot;quoted&quot; &amp; ampersanded value" />' u'<input type="file" name="email" />'
>>> w.render('email', 'test@example.com', attrs={'class': 'fun'}) >>> w.render('email', 'test@example.com', attrs={'class': 'fun'})
u'<input type="file" name="email" value="test@example.com" class="fun" />' u'<input type="file" name="email" class="fun" />'
You can also pass 'attrs' to the constructor: You can also pass 'attrs' to the constructor:
>>> w = FileInput(attrs={'class': 'fun'}) >>> w = FileInput(attrs={'class': 'fun'})
>>> w.render('email', '') >>> w.render('email', '')
u'<input type="file" class="fun" name="email" />' u'<input type="file" class="fun" name="email" />'
>>> w.render('email', 'foo@example.com') >>> w.render('email', 'foo@example.com')
u'<input type="file" class="fun" value="foo@example.com" name="email" />' u'<input type="file" class="fun" name="email" />'
>>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'}) >>> w.render('email', 'ŠĐĆŽćžšđ', attrs={'class': 'fun'})
u'<input type="file" class="fun" value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" name="email" />' u'<input type="file" class="fun" name="email" />'
# Textarea Widget ############################################################# # Textarea Widget #############################################################
@ -1532,6 +1534,42 @@ Traceback (most recent call last):
... ...
ValidationError: [u'Ensure this value has at most 15 characters (it has 20).'] ValidationError: [u'Ensure this value has at most 15 characters (it has 20).']
# FileField ##################################################################
>>> f = FileField()
>>> f.clean('')
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean(None)
Traceback (most recent call last):
...
ValidationError: [u'This field is required.']
>>> f.clean({})
Traceback (most recent call last):
...
ValidationError: [u'No file was submitted.']
>>> f.clean('some content that is not a file')
Traceback (most recent call last):
...
ValidationError: [u'No file was submitted. Check the encoding type on the form.']
>>> f.clean({'filename': 'name', 'content':None})
Traceback (most recent call last):
...
ValidationError: [u'The submitted file is empty.']
>>> f.clean({'filename': 'name', 'content':''})
Traceback (most recent call last):
...
ValidationError: [u'The submitted file is empty.']
>>> type(f.clean({'filename': 'name', 'content':'Some File Content'}))
<class 'django.newforms.fields.UploadedFile'>
# URLField ################################################################## # URLField ##################################################################
>>> f = URLField() >>> f = URLField()
@ -2573,7 +2611,7 @@ Instances of a dynamic Form do not persist fields from one Form instance to
the next. the next.
>>> class MyForm(Form): >>> class MyForm(Form):
... def __init__(self, data=None, auto_id=False, field_list=[]): ... def __init__(self, data=None, auto_id=False, field_list=[]):
... Form.__init__(self, data, auto_id) ... Form.__init__(self, data, auto_id=auto_id)
... for field in field_list: ... for field in field_list:
... self.fields[field[0]] = field[1] ... self.fields[field[0]] = field[1]
>>> field_list = [('field1', CharField()), ('field2', CharField())] >>> field_list = [('field1', CharField()), ('field2', CharField())]
@ -2591,7 +2629,7 @@ the next.
... default_field_1 = CharField() ... default_field_1 = CharField()
... default_field_2 = CharField() ... default_field_2 = CharField()
... def __init__(self, data=None, auto_id=False, field_list=[]): ... def __init__(self, data=None, auto_id=False, field_list=[]):
... Form.__init__(self, data, auto_id) ... Form.__init__(self, data, auto_id=auto_id)
... for field in field_list: ... for field in field_list:
... self.fields[field[0]] = field[1] ... self.fields[field[0]] = field[1]
>>> field_list = [('field1', CharField()), ('field2', CharField())] >>> field_list = [('field1', CharField()), ('field2', CharField())]
@ -3246,6 +3284,35 @@ is different than its data. This is handled transparently, though.
<option value="3" selected="selected">No</option> <option value="3" selected="selected">No</option>
</select> </select>
# Forms with FileFields ################################################
FileFields are a special case because they take their data from the request.FILES,
not request.POST.
>>> class FileForm(Form):
... file1 = FileField()
>>> f = FileForm(auto_id=False)
>>> print f
<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
>>> f = FileForm(data={}, files={}, auto_id=False)
>>> print f
<tr><th>File1:</th><td><ul class="errorlist"><li>This field is required.</li></ul><input type="file" name="file1" /></td></tr>
>>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':''}}, auto_id=False)
>>> print f
<tr><th>File1:</th><td><ul class="errorlist"><li>The submitted file is empty.</li></ul><input type="file" name="file1" /></td></tr>
>>> f = FileForm(data={}, files={'file1': 'something that is not a file'}, auto_id=False)
>>> print f
<tr><th>File1:</th><td><ul class="errorlist"><li>No file was submitted. Check the encoding type on the form.</li></ul><input type="file" name="file1" /></td></tr>
>>> f = FileForm(data={}, files={'file1': {'filename': 'name', 'content':'some content'}}, auto_id=False)
>>> print f
<tr><th>File1:</th><td><input type="file" name="file1" /></td></tr>
>>> f.is_valid()
True
# Basic form processing in a view ############################################# # Basic form processing in a view #############################################
>>> from django.template import Template, Context >>> from django.template import Template, Context