mirror of https://github.com/django/django.git
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:
parent
e471f42ba1
commit
fbd1a6277e
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 "quoted" & 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
|
||||||
|
|
Loading…
Reference in New Issue