diff --git a/django/core/db/backends/mysql.py b/django/core/db/backends/mysql.py index 2e77adbc43..af0dbca6c0 100644 --- a/django/core/db/backends/mysql.py +++ b/django/core/db/backends/mysql.py @@ -143,6 +143,7 @@ DATA_TYPES = { 'DateTimeField': 'datetime', 'EmailField': 'varchar(75)', 'FileField': 'varchar(100)', + 'FilePathField': 'varchar(100)', 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'ImageField': 'varchar(100)', 'IntegerField': 'integer', diff --git a/django/core/db/backends/postgresql.py b/django/core/db/backends/postgresql.py index 683ae3c9ee..6ec7bfbfcb 100644 --- a/django/core/db/backends/postgresql.py +++ b/django/core/db/backends/postgresql.py @@ -154,6 +154,7 @@ DATA_TYPES = { 'DateTimeField': 'timestamp with time zone', 'EmailField': 'varchar(75)', 'FileField': 'varchar(100)', + 'FilePathField': 'varchar(100)', 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'ImageField': 'varchar(100)', 'IntegerField': 'integer', diff --git a/django/core/db/backends/sqlite3.py b/django/core/db/backends/sqlite3.py index d4b936f82e..ea05302a61 100644 --- a/django/core/db/backends/sqlite3.py +++ b/django/core/db/backends/sqlite3.py @@ -154,6 +154,7 @@ DATA_TYPES = { 'DateTimeField': 'datetime', 'EmailField': 'varchar(75)', 'FileField': 'varchar(100)', + 'FilePathField': 'varchar(100)', 'FloatField': 'numeric(%(max_digits)s, %(decimal_places)s)', 'ImageField': 'varchar(100)', 'IntegerField': 'integer', diff --git a/django/core/formfields.py b/django/core/formfields.py index 76721ba5c6..9b9f0af1d1 100644 --- a/django/core/formfields.py +++ b/django/core/formfields.py @@ -707,6 +707,29 @@ class IPAddressField(TextField): # MISCELLANEOUS # #################### +class FilePathField(SelectField): + "A SelectField whose choices are the files in a given directory." + def __init__(self, field_name, path, match=None, recursive=False, is_required=False, validator_list=[]): + import os + if match is not None: + import re + match_re = re.compile(match) + choices = [] + if recursive: + for root, dirs, files in os.walk(path): + for f in files: + if match is None or match_re.search(f): + choices.append((os.path.join(path, f), f)) + else: + try: + for f in os.listdir(path): + full_file = os.path.join(path, f) + if os.path.isfile(full_file) and (match is None or match_re.search(f)): + choices.append((full_file, f)) + except OSError: + pass + SelectField.__init__(self, field_name, choices, 1, is_required, validator_list) + class PhoneNumberField(TextField): "A convenience FormField for validating phone numbers (e.g. '630-555-1234')" def __init__(self, field_name, is_required=False, validator_list=[]): diff --git a/django/core/meta/fields.py b/django/core/meta/fields.py index 376595230c..0740010579 100644 --- a/django/core/meta/fields.py +++ b/django/core/meta/fields.py @@ -427,6 +427,14 @@ class FileField(Field): f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename))) return os.path.normpath(f) +class FilePathField(Field): + def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): + self.path, self.match, self.recursive = path, match, recursive + Field.__init__(self, verbose_name, name, **kwargs) + + def get_manipulator_field_objs(self): + return [curry(formfields.FilePathField, path=self.path, match=self.match, recursive=self.recursive)] + class FloatField(Field): empty_strings_allowed = False def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): diff --git a/docs/model-api.txt b/docs/model-api.txt index 2ad2b3594d..19b69b2d66 100644 --- a/docs/model-api.txt +++ b/docs/model-api.txt @@ -272,6 +272,40 @@ Here are all available field types: .. _`strftime formatting`: http://docs.python.org/lib/module-time.html#l2h-1941 +``FilePathField`` + A field whose choices are limited to the filenames in a certain directory + on the filesystem. Has three special arguments, of which the first is + required: + + ====================== =================================================== + Argument Description + ====================== =================================================== + ``path`` Required. The absolute filesystem path to a + directory from which this ``FilePathField`` should + get its choices. Example: ``"/home/images"``. + + ``match`` Optional. A regular expression, as a string, that + ``FilePathField`` will use to filter filenames. + Note that the regex will be applied to the + base filename, not the full path. Example: + ``"foo.*\.txt^"``, which will match a file called + ``foo23.txt`` but not ``bar.txt`` or ``foo23.gif``. + + ``recursive`` Optional. Either ``True`` or ``False``. Default is + ``False``. Specifies whether all subdirectories of + ``path`` should be included. + + Of course, these arguments can be used together. + + The one potential gotcha is that ``match`` applies to the base filename, + not the full path. So, this example:: + + FilePathField(path="/home/images", match="foo.*", recursive=True) + + ...will match ``/home/images/foo.gif`` but not ``/home/images/foo/bar.gif`` + because the ``match`` applies to the base filename (``foo.gif`` and + ``bar.gif``). + ``FloatField`` A floating-point number. Has two **required** arguments: