diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index c5c658314..55d73fbcb 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -20,7 +20,7 @@ Each serializer field class constructor takes at least these arguments. Some Fi ### `read_only` -Read-only fields are included in the API output, but should not be included in the input during create or update operations. Any 'read_only' fields that are incorrectly included in the serializer input will be ignored. +Read-only fields are included in the API output, but should not be included in the input during create or update operations. Any 'read_only' fields that are incorrectly included in the serializer input will be ignored. Set this to `True` to ensure that the field is used when serializing a representation, but is not used when creating or updating an instance during deserialization. @@ -194,6 +194,20 @@ A field that ensures the input is a valid UUID string. The `to_internal_value` m - `'urn'` - RFC 4122 URN representation of the UUID: `"urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a"` Changing the `format` parameters only affects representation values. All formats are accepted by `to_internal_value` +## 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`. + ## IPAddressField A field that ensures the input is a valid IPv4 or IPv6 string. diff --git a/rest_framework/compat.py b/rest_framework/compat.py index b1153d666..d18541820 100644 --- a/rest_framework/compat.py +++ b/rest_framework/compat.py @@ -12,6 +12,7 @@ import django from django.conf import settings from django.core.exceptions import ImproperlyConfigured from django.db import connection, transaction +from django.forms import FilePathField as DjangoFilePathField from django.test.client import FakePayload from django.utils import six from django.utils.encoding import force_text diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 10d21c579..aa264b2aa 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -12,6 +12,7 @@ from django.conf import settings from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ObjectDoesNotExist from django.core.validators import RegexValidator, ip_address_validators +from django.forms import FilePathField as DjangoFilePathField from django.forms import ImageField as DjangoImageField from django.utils import six, timezone from django.utils.dateparse import parse_date, parse_datetime, parse_time @@ -1178,6 +1179,23 @@ class MultipleChoiceField(ChoiceField): ]) +class FilePathField(ChoiceField): + 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): + # Defer to Django's FilePathField implmentation to get the + # valid set of choices. + field = DjangoFilePathField( + path, match=match, recursive=recursive, allow_files=allow_files, + allow_folders=allow_folders, required=required + ) + kwargs['choices'] = field.choices + super(FilePathField, self).__init__(**kwargs) + + # File types... class FileField(Field): diff --git a/rest_framework/filters.py b/rest_framework/filters.py index 8c45299e7..3956bb9bc 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -191,6 +191,7 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend): perm_format = '%(app_label)s.view_%(model_name)s' def filter_queryset(self, request, queryset, view): + extra = {} user = request.user model_cls = queryset.model kwargs = { diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 93fa55640..bcc3302c1 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -305,7 +305,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/rest_framework/serializers.py b/rest_framework/serializers.py index 56dbac2d3..c7d4405c5 100644 --- a/rest_framework/serializers.py +++ b/rest_framework/serializers.py @@ -771,6 +771,7 @@ class ModelSerializer(Serializer): models.TimeField: TimeField, models.URLField: URLField, models.GenericIPAddressField: IPAddressField, + models.FilePathField: FilePathField, } if ModelDurationField is not None: serializer_field_mapping[ModelDurationField] = DurationField diff --git a/tests/test_fields.py b/tests/test_fields.py index 52ccd6362..042787357 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -1,4 +1,5 @@ import datetime +import os import uuid from decimal import Decimal @@ -637,6 +638,24 @@ class TestIPv6AddressField(FieldValues): field = serializers.IPAddressField(protocol='IPv6') +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):