diff --git a/AUTHORS b/AUTHORS index f535ddda0f..7b622b4d8d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -146,6 +146,7 @@ answer newbie questions, and generally made Django that much better: Jorge Gajon gandalf@owca.info Marc Garcia + Alex Gaynor Andy Gayton Baishampayan Ghose Dimitris Glezos diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 088d375e57..13a84ece5f 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -842,6 +842,16 @@ class FilePathField(Field): self.path, self.match, self.recursive = path, match, recursive kwargs['max_length'] = kwargs.get('max_length', 100) Field.__init__(self, verbose_name, name, **kwargs) + + def formfield(self, **kwargs): + defaults = { + 'path': self.path, + 'match': self.match, + 'recursive': self.recursive, + 'form_class': forms.FilePathField, + } + defaults.update(kwargs) + return super(FilePathField, self).formfield(**defaults) def get_manipulator_field_objs(self): return [curry(oldforms.FilePathField, path=self.path, match=self.match, recursive=self.recursive)] diff --git a/django/newforms/fields.py b/django/newforms/fields.py index bc3e543037..08e8b842ec 100644 --- a/django/newforms/fields.py +++ b/django/newforms/fields.py @@ -4,6 +4,7 @@ Field classes. import copy import datetime +import os import re import time # Python 2.3 fallbacks @@ -31,7 +32,7 @@ __all__ = ( 'RegexField', 'EmailField', 'FileField', 'ImageField', 'URLField', 'BooleanField', 'NullBooleanField', 'ChoiceField', 'MultipleChoiceField', 'ComboField', 'MultiValueField', 'FloatField', 'DecimalField', - 'SplitDateTimeField', 'IPAddressField', + 'SplitDateTimeField', 'IPAddressField', 'FilePathField', ) # These values, if given to to_python(), will trigger the self.required check. @@ -718,6 +719,33 @@ class MultiValueField(Field): """ raise NotImplementedError('Subclasses must implement this method.') +class FilePathField(ChoiceField): + def __init__(self, path, match=None, recursive=False, required=True, + widget=Select, label=None, initial=None, help_text=None, + *args, **kwargs): + self.path, self.match, self.recursive = path, match, recursive + super(FilePathField, self).__init__(choices=(), required=required, + widget=widget, label=label, initial=initial, help_text=help_text, + *args, **kwargs) + self.choices = [] + if self.match is not None: + self.match_re = re.compile(self.match) + if recursive: + for root, dirs, files in os.walk(self.path): + for f in files: + 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: + try: + for f in os.listdir(self.path): + 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)): + self.choices.append((full_file, f)) + except OSError: + pass + self.widget.choices = self.choices + class SplitDateTimeField(MultiValueField): default_error_messages = { 'invalid_date': _(u'Enter a valid date.'), diff --git a/docs/newforms.txt b/docs/newforms.txt index ff66f40e41..26dde439ef 100644 --- a/docs/newforms.txt +++ b/docs/newforms.txt @@ -1333,13 +1333,14 @@ given length. An ``UploadedFile`` object has two attributes: - ====================== ===================================================== - Argument Description - ====================== ===================================================== + ====================== ==================================================== + Attribute 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. @@ -1349,6 +1350,38 @@ When you use a ``FileField`` on a form, you must also remember to .. _`bind the file data to the form`: `Binding uploaded files to a form`_ +``FilePathField`` +~~~~~~~~~~~~~~~~~ + +**New in Django development version** + + * Default widget: ``Select`` + * Empty value: ``None`` + * Normalizes to: A unicode object + * Validates that the selected choice exists in the list of choices. + * Error message keys: ``required``, ``invalid_choice`` + +The field allows choosing from files inside a certain directory. It takes three +extra arguments: + + ============== ========== =============================================== + Argument Required? Description + ============== ========== =============================================== + ``path`` Yes The absolute path to the directory whose + contents you want listed. This directory must + exist. + + ``recursive`` No If ``False`` (the default) only the direct + contents of ``path`` will be offered as choices. + If ``True``, the directory will be descended + into recursively and all descendants will be + listed as choices. + + ``match`` No A regular expression pattern; only files with + names matching this expression will be allowed + as choices. + ============== ========== =============================================== + ``ImageField`` ~~~~~~~~~~~~~~ diff --git a/tests/regressiontests/forms/fields.py b/tests/regressiontests/forms/fields.py index 9216210e09..9421d8c005 100644 --- a/tests/regressiontests/forms/fields.py +++ b/tests/regressiontests/forms/fields.py @@ -1133,6 +1133,33 @@ u'' >>> f.clean(None) u'' +# FilePathField ############################################################### + +>>> import os +>>> from django import newforms as forms +>>> path = forms.__file__ +>>> path = os.path.dirname(path) + '/' +>>> path +'.../django/newforms/' +>>> f = forms.FilePathField(path=path) +>>> f.choices.sort() +>>> f.choices +[('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/__init__.pyc', '__init__.pyc'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/fields.pyc', 'fields.pyc'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/forms.pyc', 'forms.pyc'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/models.pyc', 'models.pyc'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/util.pyc', 'util.pyc'), ('.../django/newforms/widgets.py', 'widgets.py'), ('.../django/newforms/widgets.pyc', 'widgets.pyc')] +>>> f.clean('fields.py') +Traceback (most recent call last): +... +ValidationError: [u'Select a valid choice. That choice is not one of the available choices.'] +>>> f.clean(path + 'fields.py') +u'.../django/newforms/fields.py' +>>> f = forms.FilePathField(path=path, match='^.*?\.py$') +>>> f.choices.sort() +>>> f.choices +[('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/widgets.py', 'widgets.py')] +>>> f = forms.FilePathField(path=path, recursive=True, match='^.*?\.py$') +>>> f.choices.sort() +>>> f.choices +[('.../django/newforms/__init__.py', '__init__.py'), ('.../django/newforms/extras/__init__.py', 'extras/__init__.py'), ('.../django/newforms/extras/widgets.py', 'extras/widgets.py'), ('.../django/newforms/fields.py', 'fields.py'), ('.../django/newforms/forms.py', 'forms.py'), ('.../django/newforms/models.py', 'models.py'), ('.../django/newforms/util.py', 'util.py'), ('.../django/newforms/widgets.py', 'widgets.py')] + # SplitDateTimeField ########################################################## >>> f = SplitDateTimeField()