Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit 3c5ff9d703be67b43655c5a40e8ee3719ad2871b 1 parent 83fc965
Alex Gaynor authored April 22, 2012
2  django/core/management/validation.py
@@ -89,6 +89,8 @@ def get_validation_errors(outfile, app=None):
89 89
                         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)
90 90
             if isinstance(f, models.BooleanField) and getattr(f, 'null', False):
91 91
                 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)
92 94
             if f.choices:
93 95
                 if isinstance(f.choices, basestring) or not is_iterable(f.choices):
94 96
                     e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name)
5  django/db/models/fields/__init__.py
@@ -909,8 +909,9 @@ class FilePathField(Field):
909 909
     description = _("File path")
910 910
 
911 911
     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):
913 913
         self.path, self.match, self.recursive = path, match, recursive
  914
+        self.allow_files, self.allow_folders =  allow_files, allow_folders
914 915
         kwargs['max_length'] = kwargs.get('max_length', 100)
915 916
         Field.__init__(self, verbose_name, name, **kwargs)
916 917
 
@@ -920,6 +921,8 @@ def formfield(self, **kwargs):
920 921
             'match': self.match,
921 922
             'recursive': self.recursive,
922 923
             'form_class': forms.FilePathField,
  924
+            'allow_files': self.allow_files,
  925
+            'allow_folders': self.allow_folders,
923 926
         }
924 927
         defaults.update(kwargs)
925 928
         return super(FilePathField, self).formfield(**defaults)
25  django/forms/fields.py
@@ -909,10 +909,11 @@ def compress(self, data_list):
909 909
         raise NotImplementedError('Subclasses must implement this method.')
910 910
 
911 911
 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):
915 915
         self.path, self.match, self.recursive = path, match, recursive
  916
+        self.allow_files, self.allow_folders = allow_files, allow_folders
916 917
         super(FilePathField, self).__init__(choices=(), required=required,
917 918
             widget=widget, label=label, initial=initial, help_text=help_text,
918 919
             *args, **kwargs)
@@ -927,15 +928,23 @@ def __init__(self, path, match=None, recursive=False, required=True,
927 928
 
928 929
         if recursive:
929 930
             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)))
934 941
         else:
935 942
             try:
936 943
                 for f in sorted(os.listdir(self.path)):
937 944
                     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))):
939 948
                         self.choices.append((full_file, f))
940 949
             except OSError:
941 950
                 pass
17  docs/ref/forms/fields.txt
@@ -555,6 +555,23 @@ For each field, we describe the default widget used if you don't specify
555 555
         A regular expression pattern; only files with names matching this expression
556 556
         will be allowed as choices.
557 557
 
  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
+
558 575
 ``FloatField``
559 576
 ~~~~~~~~~~~~~~
560 577
 
17  docs/ref/models/fields.txt
@@ -691,6 +691,23 @@ directory on the filesystem. Has three special arguments, of which the first is
691 691
     Optional. Either ``True`` or ``False``. Default is ``False``. Specifies
692 692
     whether all subdirectories of :attr:`~FilePathField.path` should be included
693 693
 
  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
+
694 711
 Of course, these arguments can be used together.
695 712
 
696 713
 The one potential gotcha is that :attr:`~FilePathField.match` applies to the
36  tests/regressiontests/forms/tests/fields.py
@@ -981,6 +981,42 @@ def test_filepathfield_4(self):
981 981
             self.assertEqual(exp[1], got[1])
982 982
             self.assertTrue(got[0].endswith(exp[0]))
983 983
 
  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
+
984 1020
     # SplitDateTimeField ##########################################################
985 1021
 
986 1022
     def test_splitdatetimefield_1(self):

0 notes on commit 3c5ff9d

Please sign in to comment.
Something went wrong with that request. Please try again.