Skip to content

Commit 3c5ff9d

Browse files
committed
Fixed django#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
1 parent 83fc965 commit 3c5ff9d

File tree

6 files changed

+93
-9
lines changed

6 files changed

+93
-9
lines changed

django/core/management/validation.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ def get_validation_errors(outfile, app=None):
8989
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)
9090
if isinstance(f, models.BooleanField) and getattr(f, 'null', False):
9191
e.add(opts, '"%s": BooleanFields do not accept null values. Use a NullBooleanField instead.' % f.name)
92+
if isinstance(f, models.FilePathField) and not (f.allow_files or f.allow_folders):
93+
e.add(opts, '"%s": FilePathFields must have either allow_files or allow_folders set to True.' % f.name)
9294
if f.choices:
9395
if isinstance(f.choices, basestring) or not is_iterable(f.choices):
9496
e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)

django/db/models/fields/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -909,8 +909,9 @@ class FilePathField(Field):
909909
description = _("File path")
910910

911911
def __init__(self, verbose_name=None, name=None, path='', match=None,
912-
recursive=False, **kwargs):
912+
recursive=False, allow_files=True, allow_folders=False, **kwargs):
913913
self.path, self.match, self.recursive = path, match, recursive
914+
self.allow_files, self.allow_folders = allow_files, allow_folders
914915
kwargs['max_length'] = kwargs.get('max_length', 100)
915916
Field.__init__(self, verbose_name, name, **kwargs)
916917

@@ -920,6 +921,8 @@ def formfield(self, **kwargs):
920921
'match': self.match,
921922
'recursive': self.recursive,
922923
'form_class': forms.FilePathField,
924+
'allow_files': self.allow_files,
925+
'allow_folders': self.allow_folders,
923926
}
924927
defaults.update(kwargs)
925928
return super(FilePathField, self).formfield(**defaults)

django/forms/fields.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -909,10 +909,11 @@ def compress(self, data_list):
909909
raise NotImplementedError('Subclasses must implement this method.')
910910

911911
class FilePathField(ChoiceField):
912-
def __init__(self, path, match=None, recursive=False, required=True,
913-
widget=None, label=None, initial=None, help_text=None,
914-
*args, **kwargs):
912+
def __init__(self, path, match=None, recursive=False, allow_files=True,
913+
allow_folders=False, required=True, widget=None, label=None,
914+
initial=None, help_text=None, *args, **kwargs):
915915
self.path, self.match, self.recursive = path, match, recursive
916+
self.allow_files, self.allow_folders = allow_files, allow_folders
916917
super(FilePathField, self).__init__(choices=(), required=required,
917918
widget=widget, label=label, initial=initial, help_text=help_text,
918919
*args, **kwargs)
@@ -927,15 +928,23 @@ def __init__(self, path, match=None, recursive=False, required=True,
927928

928929
if recursive:
929930
for root, dirs, files in sorted(os.walk(self.path)):
930-
for f in files:
931-
if self.match is None or self.match_re.search(f):
932-
f = os.path.join(root, f)
933-
self.choices.append((f, f.replace(path, "", 1)))
931+
if self.allow_files:
932+
for f in files:
933+
if self.match is None or self.match_re.search(f):
934+
f = os.path.join(root, f)
935+
self.choices.append((f, f.replace(path, "", 1)))
936+
if self.allow_folders:
937+
for f in dirs:
938+
if self.match is None or self.match_re.search(f):
939+
f = os.path.join(root, f)
940+
self.choices.append((f, f.replace(path, "", 1)))
934941
else:
935942
try:
936943
for f in sorted(os.listdir(self.path)):
937944
full_file = os.path.join(self.path, f)
938-
if os.path.isfile(full_file) and (self.match is None or self.match_re.search(f)):
945+
if (((self.allow_files and os.path.isfile(full_file)) or
946+
(self.allow_folders and os.path.isdir(full_file))) and
947+
(self.match is None or self.match_re.search(f))):
939948
self.choices.append((full_file, f))
940949
except OSError:
941950
pass

docs/ref/forms/fields.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -555,6 +555,23 @@ For each field, we describe the default widget used if you don't specify
555555
A regular expression pattern; only files with names matching this expression
556556
will be allowed as choices.
557557

558+
.. attribute:: allow_files
559+
560+
.. versionadded:: 1.5
561+
562+
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
563+
whether files in the specified location should be included. Either this or
564+
:attr:`allow_folders` must be ``True``.
565+
566+
.. attribute:: allow_folders
567+
568+
.. versionadded:: 1.5
569+
570+
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
571+
whether folders in the specified location should be included. Either this or
572+
:attr:`allow_files` must be ``True``.
573+
574+
558575
``FloatField``
559576
~~~~~~~~~~~~~~
560577

docs/ref/models/fields.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,23 @@ directory on the filesystem. Has three special arguments, of which the first is
691691
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
692692
whether all subdirectories of :attr:`~FilePathField.path` should be included
693693

694+
.. attribute:: FilePathField.allow_files
695+
696+
.. versionadded:: 1.5
697+
698+
Optional. Either ``True`` or ``False``. Default is ``True``. Specifies
699+
whether files in the specified location should be included. Either this or
700+
:attr:`~FilePathField.allow_folders` must be ``True``.
701+
702+
.. attribute:: FilePathField.allow_folders
703+
704+
.. versionadded:: 1.5
705+
706+
Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
707+
whether folders in the specified location should be included. Either this
708+
or :attr:`~FilePathField.allow_files` must be ``True``.
709+
710+
694711
Of course, these arguments can be used together.
695712

696713
The one potential gotcha is that :attr:`~FilePathField.match` applies to the

tests/regressiontests/forms/tests/fields.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,42 @@ def test_filepathfield_4(self):
981981
self.assertEqual(exp[1], got[1])
982982
self.assertTrue(got[0].endswith(exp[0]))
983983

984+
def test_filepathfield_folders(self):
985+
path = forms.__file__
986+
path = os.path.dirname(path) + '/'
987+
f = FilePathField(path=path, allow_folders=True, allow_files=False)
988+
f.choices.sort()
989+
expected = [
990+
('/django/forms/extras', 'extras'),
991+
]
992+
for exp, got in zip(expected, fix_os_paths(f.choices)):
993+
self.assertEqual(exp[1], got[1])
994+
self.assert_(got[0].endswith(exp[0]))
995+
996+
f = FilePathField(path=path, allow_folders=True, allow_files=True)
997+
f.choices.sort()
998+
expected = [
999+
('/django/forms/__init__.py', '__init__.py'),
1000+
('/django/forms/__init__.pyc', '__init__.pyc'),
1001+
('/django/forms/extras', 'extras'),
1002+
('/django/forms/fields.py', 'fields.py'),
1003+
('/django/forms/fields.pyc', 'fields.pyc'),
1004+
('/django/forms/forms.py', 'forms.py'),
1005+
('/django/forms/forms.pyc', 'forms.pyc'),
1006+
('/django/forms/formsets.py', 'formsets.py'),
1007+
('/django/forms/formsets.pyc', 'formsets.pyc'),
1008+
('/django/forms/models.py', 'models.py'),
1009+
('/django/forms/models.pyc', 'models.pyc'),
1010+
('/django/forms/util.py', 'util.py'),
1011+
('/django/forms/util.pyc', 'util.pyc'),
1012+
('/django/forms/widgets.py', 'widgets.py'),
1013+
('/django/forms/widgets.pyc', 'widgets.pyc')
1014+
]
1015+
for exp, got in zip(expected, fix_os_paths(f.choices)):
1016+
self.assertEqual(exp[1], got[1])
1017+
self.assertEqual(exp[1], got[1])
1018+
1019+
9841020
# SplitDateTimeField ##########################################################
9851021

9861022
def test_splitdatetimefield_1(self):

0 commit comments

Comments
 (0)