diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index f113bb232..1117fb8c1 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -189,6 +189,20 @@ A field that ensures the input is a valid UUID string. The `to_internal_value` m "de305d54-75b4-431b-adb2-eb6b9e546013" +## FilePathField + +A field whose choices are limited to the filenames in a certain directory on the filesystem + +Corresponds to `django.forms.fields.FilePathField`. + +**Signature:** `FilePathField(path, match=None, recursive=False, allow_files=True, allow_folders=False, required=None, **kwargs)` + +- `path` - The absolute filesystem path to a directory from which this FilePathField should get its choice. +- `match` - A regular expression, as a string, that FilePathField will use to filter filenames. +- `recursive` - Specifies whether all subdirectories of path should be included. Default is `False`. +- `allow_files` - Specifies whether files in the specified location should be included. Default is `True`. Either this or `allow_folders` must be `True`. +- `allow_folders` - Specifies whether folders in the specified location should be included. Default is `False`. Either this or `allow_files` must be `True`. + --- # Numeric fields diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 13301f31b..e99e6047e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -3,7 +3,9 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ValidationError as DjangoValidationError from django.core.validators import RegexValidator -from django.forms import ImageField as DjangoImageField +from django.forms import ( + ImageField as DjangoImageField, FilePathField as DjangoFilePathField +) from django.utils import six, timezone from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.encoding import is_protected_type, smart_text @@ -653,6 +655,40 @@ class UUIDField(Field): return str(value) +class FilePathField(CharField): + default_error_messages = { + 'invalid_choice': _('"{input}" is not a valid path choice.') + } + + def __init__(self, path, match=None, recursive=False, allow_files=True, + allow_folders=False, required=None, **kwargs): + super(FilePathField, self).__init__(**kwargs) + # create field and get options to avoid code duplication + field = DjangoFilePathField( + path, match=match, recursive=recursive, allow_files=allow_files, + allow_folders=allow_folders, required=required + ) + + self.choices = OrderedDict(field.choices) + self.choice_strings_to_values = { + six.text_type(key): key for key in self.choices.keys() + } + + def to_internal_value(self, data): + if data == '' and self.allow_blank: + return '' + + try: + return self.choice_strings_to_values[six.text_type(data)] + except KeyError: + self.fail('invalid_choice', input=data) + + def to_representation(self, value): + if value in ('', None): + return value + return self.choice_strings_to_values[six.text_type(value)] + + # Number types... class IntegerField(Field): diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 920d2bc47..134b41b36 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -301,7 +301,10 @@ class HTMLFormRenderer(BaseRenderer): }, serializers.ListSerializer: { 'base_template': 'list_fieldset.html' - } + }, + serializers.FilePathField: { + 'base_template': 'select.html', + }, }) def render_field(self, field, parent_style): diff --git a/tests/test_fields.py b/tests/test_fields.py index 7f5f81029..3d2138a01 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1,10 +1,12 @@ -from decimal import Decimal -from django.utils import timezone -from rest_framework import serializers import datetime +import os +import uuid +from decimal import Decimal + import django import pytest -import uuid +from django.utils import timezone +from rest_framework import serializers # Tests for field keyword arguments and core functionality. @@ -518,6 +520,24 @@ class TestUUIDField(FieldValues): field = serializers.UUIDField() +class TestFilePathField(FieldValues): + """ + Valid and invalid values for `FilePathField` + """ + + valid_inputs = { + __file__: __file__, + } + invalid_inputs = { + 'wrong_path': ['"wrong_path" is not a valid path choice.'] + } + outputs = { + } + field = serializers.FilePathField( + path=os.path.abspath(os.path.dirname(__file__)) + ) + + # Number types... class TestIntegerField(FieldValues):