Fixed #5893 -- Added a flag to FilePathField to allow listing folders, in addition to regular files. Thank you to Brian Rosner, for encouraging me to first contribute to Django 4 years ago.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@17925 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
83fc965171
commit
3c5ff9d703
|
@ -89,6 +89,8 @@ def get_validation_errors(outfile, app=None):
|
||||||
e.add(opts, '"%s": To use ImageFields, you need to install the Python Imaging Library. Get it at http://www.pythonware.com/products/pil/ .' % f.name)
|
e.add(opts, '"%s": To use ImageFields, you need to install the Python Imaging Library. Get it at http://www.pythonware.com/products/pil/ .' % f.name)
|
||||||
if isinstance(f, models.BooleanField) and getattr(f, 'null', False):
|
if isinstance(f, models.BooleanField) and getattr(f, 'null', False):
|
||||||
e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name)
|
e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name)
|
||||||
|
if isinstance(f, models.FilePathField) and not (f.allow_files or f.allow_folders):
|
||||||
|
e.add(opts, '"%s": FilePathFields must have either allow_files or allow_folders set to True.' % f.name)
|
||||||
if f.choices:
|
if f.choices:
|
||||||
if isinstance(f.choices, basestring) or not is_iterable(f.choices):
|
if isinstance(f.choices, basestring) or not is_iterable(f.choices):
|
||||||
e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
|
e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
|
||||||
|
|
|
@ -909,8 +909,9 @@ class FilePathField(Field):
|
||||||
description = _("File path")
|
description = _("File path")
|
||||||
|
|
||||||
def __init__(self, verbose_name=None, name=None, path='', match=None,
|
def __init__(self, verbose_name=None, name=None, path='', match=None,
|
||||||
recursive=False, **kwargs):
|
recursive=False, allow_files=True, allow_folders=False, **kwargs):
|
||||||
self.path, self.match, self.recursive = path, match, recursive
|
self.path, self.match, self.recursive = path, match, recursive
|
||||||
|
self.allow_files, self.allow_folders = allow_files, allow_folders
|
||||||
kwargs['max_length'] = kwargs.get('max_length', 100)
|
kwargs['max_length'] = kwargs.get('max_length', 100)
|
||||||
Field.__init__(self, verbose_name, name, **kwargs)
|
Field.__init__(self, verbose_name, name, **kwargs)
|
||||||
|
|
||||||
|
@ -920,6 +921,8 @@ class FilePathField(Field):
|
||||||
'match': self.match,
|
'match': self.match,
|
||||||
'recursive': self.recursive,
|
'recursive': self.recursive,
|
||||||
'form_class': forms.FilePathField,
|
'form_class': forms.FilePathField,
|
||||||
|
'allow_files': self.allow_files,
|
||||||
|
'allow_folders': self.allow_folders,
|
||||||
}
|
}
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
return super(FilePathField, self).formfield(**defaults)
|
return super(FilePathField, self).formfield(**defaults)
|
||||||
|
|
|
@ -909,10 +909,11 @@ class MultiValueField(Field):
|
||||||
raise NotImplementedError('Subclasses must implement this method.')
|
raise NotImplementedError('Subclasses must implement this method.')
|
||||||
|
|
||||||
class FilePathField(ChoiceField):
|
class FilePathField(ChoiceField):
|
||||||
def __init__(self, path, match=None, recursive=False, required=True,
|
def __init__(self, path, match=None, recursive=False, allow_files=True,
|
||||||
widget=None, label=None, initial=None, help_text=None,
|
allow_folders=False, required=True, widget=None, label=None,
|
||||||
*args, **kwargs):
|
initial=None, help_text=None, *args, **kwargs):
|
||||||
self.path, self.match, self.recursive = path, match, recursive
|
self.path, self.match, self.recursive = path, match, recursive
|
||||||
|
self.allow_files, self.allow_folders = allow_files, allow_folders
|
||||||
super(FilePathField, self).__init__(choices=(), required=required,
|
super(FilePathField, self).__init__(choices=(), required=required,
|
||||||
widget=widget, label=label, initial=initial, help_text=help_text,
|
widget=widget, label=label, initial=initial, help_text=help_text,
|
||||||
*args, **kwargs)
|
*args, **kwargs)
|
||||||
|
@ -927,15 +928,23 @@ class FilePathField(ChoiceField):
|
||||||
|
|
||||||
if recursive:
|
if recursive:
|
||||||
for root, dirs, files in sorted(os.walk(self.path)):
|
for root, dirs, files in sorted(os.walk(self.path)):
|
||||||
for f in files:
|
if self.allow_files:
|
||||||
if self.match is None or self.match_re.search(f):
|
for f in files:
|
||||||
f = os.path.join(root, f)
|
if self.match is None or self.match_re.search(f):
|
||||||
self.choices.append((f, f.replace(path, "", 1)))
|
f = os.path.join(root, f)
|
||||||
|
self.choices.append((f, f.replace(path, "", 1)))
|
||||||
|
if self.allow_folders:
|
||||||
|
for f in dirs:
|
||||||
|
if self.match is None or self.match_re.search(f):
|
||||||
|
f = os.path.join(root, f)
|
||||||
|
self.choices.append((f, f.replace(path, "", 1)))
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
for f in sorted(os.listdir(self.path)):
|
for f in sorted(os.listdir(self.path)):
|
||||||
full_file = os.path.join(self.path, f)
|
full_file = os.path.join(self.path, f)
|
||||||
if os.path.isfile(full_file) and (self.match is None or self.match_re.search(f)):
|
if (((self.allow_files and os.path.isfile(full_file)) or
|
||||||
|
(self.allow_folders and os.path.isdir(full_file))) and
|
||||||
|
(self.match is None or self.match_re.search(f))):
|
||||||
self.choices.append((full_file, f))
|
self.choices.append((full_file, f))
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -555,6 +555,23 @@ For each field, we describe the default widget used if you don't specify
|
||||||
A regular expression pattern; only files with names matching this expression
|
A regular expression pattern; only files with names matching this expression
|
||||||
will be allowed as choices.
|
will be allowed as choices.
|
||||||
|
|
||||||
|
.. attribute:: allow_files
|
||||||
|
|
||||||
|
.. versionadded:: 1.5
|
||||||
|
|
||||||
|
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
|
||||||
|
whether files in the specified location should be included. Either this or
|
||||||
|
:attr:`allow_folders` must be ``True``.
|
||||||
|
|
||||||
|
.. attribute:: allow_folders
|
||||||
|
|
||||||
|
.. versionadded:: 1.5
|
||||||
|
|
||||||
|
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
|
||||||
|
whether folders in the specified location should be included. Either this or
|
||||||
|
:attr:`allow_files` must be ``True``.
|
||||||
|
|
||||||
|
|
||||||
``FloatField``
|
``FloatField``
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -691,6 +691,23 @@ directory on the filesystem. Has three special arguments, of which the first is
|
||||||
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
|
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
|
||||||
whether all subdirectories of :attr:`~FilePathField.path` should be included
|
whether all subdirectories of :attr:`~FilePathField.path` should be included
|
||||||
|
|
||||||
|
.. attribute:: FilePathField.allow_files
|
||||||
|
|
||||||
|
.. versionadded:: 1.5
|
||||||
|
|
||||||
|
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
|
||||||
|
whether files in the specified location should be included. Either this or
|
||||||
|
:attr:`~FilePathField.allow_folders` must be ``True``.
|
||||||
|
|
||||||
|
.. attribute:: FilePathField.allow_folders
|
||||||
|
|
||||||
|
.. versionadded:: 1.5
|
||||||
|
|
||||||
|
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
|
||||||
|
whether folders in the specified location should be included. Either this
|
||||||
|
or :attr:`~FilePathField.allow_files` must be ``True``.
|
||||||
|
|
||||||
|
|
||||||
Of course, these arguments can be used together.
|
Of course, these arguments can be used together.
|
||||||
|
|
||||||
The one potential gotcha is that :attr:`~FilePathField.match` applies to the
|
The one potential gotcha is that :attr:`~FilePathField.match` applies to the
|
||||||
|
|
|
@ -981,6 +981,42 @@ class FieldsTests(SimpleTestCase):
|
||||||
self.assertEqual(exp[1], got[1])
|
self.assertEqual(exp[1], got[1])
|
||||||
self.assertTrue(got[0].endswith(exp[0]))
|
self.assertTrue(got[0].endswith(exp[0]))
|
||||||
|
|
||||||
|
def test_filepathfield_folders(self):
|
||||||
|
path = forms.__file__
|
||||||
|
path = os.path.dirname(path) + '/'
|
||||||
|
f = FilePathField(path=path, allow_folders=True, allow_files=False)
|
||||||
|
f.choices.sort()
|
||||||
|
expected = [
|
||||||
|
('/django/forms/extras', 'extras'),
|
||||||
|
]
|
||||||
|
for exp, got in zip(expected, fix_os_paths(f.choices)):
|
||||||
|
self.assertEqual(exp[1], got[1])
|
||||||
|
self.assert_(got[0].endswith(exp[0]))
|
||||||
|
|
||||||
|
f = FilePathField(path=path, allow_folders=True, allow_files=True)
|
||||||
|
f.choices.sort()
|
||||||
|
expected = [
|
||||||
|
('/django/forms/__init__.py', '__init__.py'),
|
||||||
|
('/django/forms/__init__.pyc', '__init__.pyc'),
|
||||||
|
('/django/forms/extras', 'extras'),
|
||||||
|
('/django/forms/fields.py', 'fields.py'),
|
||||||
|
('/django/forms/fields.pyc', 'fields.pyc'),
|
||||||
|
('/django/forms/forms.py', 'forms.py'),
|
||||||
|
('/django/forms/forms.pyc', 'forms.pyc'),
|
||||||
|
('/django/forms/formsets.py', 'formsets.py'),
|
||||||
|
('/django/forms/formsets.pyc', 'formsets.pyc'),
|
||||||
|
('/django/forms/models.py', 'models.py'),
|
||||||
|
('/django/forms/models.pyc', 'models.pyc'),
|
||||||
|
('/django/forms/util.py', 'util.py'),
|
||||||
|
('/django/forms/util.pyc', 'util.pyc'),
|
||||||
|
('/django/forms/widgets.py', 'widgets.py'),
|
||||||
|
('/django/forms/widgets.pyc', 'widgets.pyc')
|
||||||
|
]
|
||||||
|
for exp, got in zip(expected, fix_os_paths(f.choices)):
|
||||||
|
self.assertEqual(exp[1], got[1])
|
||||||
|
self.assertEqual(exp[1], got[1])
|
||||||
|
|
||||||
|
|
||||||
# SplitDateTimeField ##########################################################
|
# SplitDateTimeField ##########################################################
|
||||||
|
|
||||||
def test_splitdatetimefield_1(self):
|
def test_splitdatetimefield_1(self):
|
||||||
|
|
Loading…
Reference in New Issue