Skip to content

Commit

Permalink
Merge branch 'feature/filepathfield' of https://github.com/Ins1ne/dja…
Browse files Browse the repository at this point in the history
…ngo-rest-framework into Ins1ne-feature/filepathfield
  • Loading branch information
tomchristie committed Aug 3, 2015
2 parents 650866c + 205f388 commit 8d7c0a8
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 3 deletions.
16 changes: 15 additions & 1 deletion docs/api-guide/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Expand Down
19 changes: 19 additions & 0 deletions rest_framework/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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
Expand Down Expand Up @@ -256,3 +257,21 @@ def set_rollback():
else:
# transaction not managed
pass


def get_filepathfield(path, match=None, recursive=False, allow_files=True,
allow_folders=False, required=None):
"""Create proper Django FilePathField with allowed kwargs."""

if django.VERSION < (1, 5):
# django field doesn't have allow_folders, allow_files kwargs
field = DjangoFilePathField(
path, match=match, recursive=recursive, required=required
)
else:
field = DjangoFilePathField(
path, match=match, recursive=recursive, allow_files=allow_files,
allow_folders=allow_folders, required=required
)

return field
37 changes: 36 additions & 1 deletion rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from rest_framework.compat import (
EmailValidator, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
MinValueValidator, OrderedDict, URLValidator, duration_string,
parse_duration, unicode_repr, unicode_to_repr
get_filepathfield, parse_duration, unicode_repr, unicode_to_repr
)
from rest_framework.exceptions import ValidationError
from rest_framework.settings import api_settings
Expand Down Expand Up @@ -704,6 +704,41 @@ def to_internal_value(self, data):
return super(IPAddressField, self).to_internal_value(data)


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 = get_filepathfield(
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 = dict([
(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):
Expand Down
1 change: 1 addition & 0 deletions rest_framework/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ def __init__(self):
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 = {
Expand Down
5 changes: 4 additions & 1 deletion rest_framework/renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
1 change: 1 addition & 0 deletions rest_framework/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import datetime
import os
import uuid
from decimal import Decimal

Expand Down Expand Up @@ -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):
Expand Down

0 comments on commit 8d7c0a8

Please sign in to comment.