Skip to content

Commit

Permalink
Merge master
Browse files Browse the repository at this point in the history
  • Loading branch information
tomchristie committed Jan 30, 2015
2 parents 8f33e39 + 7cf9dea commit 4ee4b4f
Show file tree
Hide file tree
Showing 29 changed files with 316 additions and 70 deletions.
29 changes: 26 additions & 3 deletions docs/api-guide/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ Corresponds to `django.db.models.fields.URLField`. Uses Django's `django.core.v

**Signature:** `URLField(max_length=200, min_length=None, allow_blank=False)`

## UUIDField

A field that ensures the input is a valid UUID string. The `to_internal_value` method will return a `uuid.UUID` instance. On output the field will return a string in the canonical hyphenated format, for example:

"de305d54-75b4-431b-adb2-eb6b9e546013"

---

# Numeric fields
Expand Down Expand Up @@ -320,7 +326,7 @@ Both the `allow_blank` and `allow_null` are valid options on `ChoiceField`, alth

## MultipleChoiceField

A field that can accept a set of zero, one or many values, chosen from a limited set of choices. Takes a single mandatory argument. `to_internal_representation` returns a `set` containing the selected values.
A field that can accept a set of zero, one or many values, chosen from a limited set of choices. Takes a single mandatory argument. `to_internal_value` returns a `set` containing the selected values.

**Signature:** `MultipleChoiceField(choices)`

Expand Down Expand Up @@ -374,7 +380,7 @@ A field class that validates a list of objects.

**Signature**: `ListField(child)`

- `child` - A field instance that should be used for validating the objects in the list.
- `child` - A field instance that should be used for validating the objects in the list. If this argument is not provided then objects in the list will not be validated.

For example, to validate a list of integers you might use something like the following:

Expand All @@ -389,6 +395,23 @@ The `ListField` class also supports a declarative style that allows you to write

We can now reuse our custom `StringListField` class throughout our application, without having to provide a `child` argument to it.

## DictField

A field class that validates a dictionary of objects. The keys in `DictField` are always assumed to be string values.

**Signature**: `DictField(child)`

- `child` - A field instance that should be used for validating the values in the dictionary. If this argument is not provided then values in the mapping will not be validated.

For example, to create a field that validates a mapping of strings to strings, you would write something like this:

document = DictField(child=CharField())

You can also use the declarative style, as with `ListField`. For example:

class DocumentField(DictField):
child = CharField()

---

# Miscellaneous fields
Expand Down Expand Up @@ -438,7 +461,7 @@ This is a read-only field. It gets its value by calling a method on the serializ

**Signature**: `SerializerMethodField(method_name=None)`

- `method-name` - The name of the method on the serializer to be called. If not included this defaults to `get_<field_name>`.
- `method_name` - The name of the method on the serializer to be called. If not included this defaults to `get_<field_name>`.

The serializer method referred to by the `method_name` argument should accept a single argument (in addition to `self`), which is the object being serialized. It should return whatever you want to be included in the serialized representation of the object. For example:

Expand Down
4 changes: 2 additions & 2 deletions docs/api-guide/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,8 @@ The [django-rest-framework-filters package][django-rest-framework-filters] works
[cite]: https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters
[django-filter]: https://github.com/alex/django-filter
[django-filter-docs]: https://django-filter.readthedocs.org/en/latest/index.html
[guardian]: http://pythonhosted.org/django-guardian/
[view-permissions]: http://pythonhosted.org/django-guardian/userguide/assign.html
[guardian]: https://django-guardian.readthedocs.org/
[view-permissions]: https://django-guardian.readthedocs.org/en/latest/userguide/assign.html
[view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models
[nullbooleanselect]: https://github.com/django/django/blob/master/django/forms/widgets.py
[search-django-admin]: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
Expand Down
8 changes: 2 additions & 6 deletions docs/api-guide/generic-views.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,17 +93,13 @@ The following attributes are used to control pagination when used with list view

* `filter_backends` - A list of filter backend classes that should be used for filtering the queryset. Defaults to the same value as the `DEFAULT_FILTER_BACKENDS` setting.

**Deprecated attributes**:

* `model` - This shortcut may be used instead of setting either (or both) of the `queryset`/`serializer_class` attributes. The explicit style is preferred over the `.model` shortcut, and usage of this attribute is now deprecated.

### Methods

**Base methods**:

#### `get_queryset(self)`

Returns the queryset that should be used for list views, and that should be used as the base for lookups in detail views. Defaults to returning the queryset specified by the `queryset` attribute, or the default queryset for the model if the `model` shortcut is being used.
Returns the queryset that should be used for list views, and that should be used as the base for lookups in detail views. Defaults to returning the queryset specified by the `queryset` attribute.

This method should always be used rather than accessing `self.queryset` directly, as `self.queryset` gets evaluated only once, and those results are cached for all subsequent requests.

Expand Down Expand Up @@ -153,7 +149,7 @@ For example:

#### `get_serializer_class(self)`

Returns the class that should be used for the serializer. Defaults to returning the `serializer_class` attribute, or dynamically generating a serializer class if the `model` shortcut is being used.
Returns the class that should be used for the serializer. Defaults to returning the `serializer_class` attribute.

May be overridden to provide dynamic behavior, such as using different serializers for read and write operations, or providing different serializers to different types of users.

Expand Down
2 changes: 1 addition & 1 deletion docs/api-guide/parsers.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword
def put(self, request, filename, format=None):
file_obj = request.data['file']
# ...
# do some staff with uploaded file
# do some stuff with uploaded file
# ...
return Response(status=204)

Expand Down
12 changes: 6 additions & 6 deletions docs/api-guide/routers.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ There are two mandatory arguments to the `register()` method:

Optionally, you may also specify an additional argument:

* `base_name` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `model` or `queryset` attribute on the viewset, if it has one. Note that if the viewset does not include a `model` or `queryset` attribute then you must set `base_name` when registering the viewset.
* `base_name` - The base to use for the URL names that are created. If unset the basename will be automatically generated based on the `queryset` attribute of the viewset, if it has one. Note that if the viewset does not include a `queryset` attribute then you must set `base_name` when registering the viewset.

The example above would generate the following URL patterns:

Expand Down Expand Up @@ -60,23 +60,23 @@ For example, you can append `router.urls` to a list of existing views…
router.register(r'accounts', AccountViewSet)

urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view(),
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
]

urlpatterns += router.urls

Alternatively you can use Django's `include` function, like so…

urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view(),
url(r'^', include(router.urls))
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
url(r'^', include(router.urls)),
]

Router URL patterns can also be namespaces.

urlpatterns = [
url(r'^forgot-password/$', ForgotPasswordFormView.as_view(),
url(r'^api/', include(router.urls, namespace='api'))
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
url(r'^api/', include(router.urls, namespace='api')),
]

If using namespacing with hyperlinked serializers you'll also need to ensure that any `view_name` parameters on the serializers correctly reflect the namespace. In the example above you'd need to include a parameter such as `view_name='api:user-detail'` for serializer fields hyperlinked to the user detail view.
Expand Down
2 changes: 1 addition & 1 deletion docs/api-guide/viewsets.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ The decorators can additionally take extra arguments that will be set for the ro
def set_password(self, request, pk=None):
...

Theses decorators will route `GET` requests by default, but may also accept other HTTP methods, by using the `methods` argument. For example:
These decorators will route `GET` requests by default, but may also accept other HTTP methods, by using the `methods` argument. For example:

@detail_route(methods=['post', 'delete'])
def unset_password(self, request, pk=None):
Expand Down
Binary file removed docs/img/sponsors/2-galileo_press.png
Binary file not shown.
Binary file added docs/img/sponsors/2-rheinwerk_verlag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ REST framework requires the following:
The following packages are optional:

* [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API.
* [django-filter][django-filter] (0.5.4+) - Filtering support.
* [django-filter][django-filter] (0.9.2+) - Filtering support.
* [django-guardian][django-guardian] (1.1.1+) - Object level permissions support.

## Installation
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/3.0-announcement.md
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,7 @@ This code *would be valid* in `2.4.3`:
class Meta:
model = Account

However this code *would not be valid* in `2.4.3`:
However this code *would not be valid* in `3.0`:

# Missing `queryset`
class AccountSerializer(serializers.Serializer):
Expand Down
2 changes: 1 addition & 1 deletion docs/topics/kickstarter-announcement.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ Our gold sponsors include companies large and small. Many thanks for their signi
<li><a href="http://pulsecode.ca" rel="nofollow" style="background-image:url(../../img/sponsors/2-pulsecode.png);">Pulsecode Inc.</a></li>
<li><a href="http://singinghorsestudio.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-singing-horse.png);">Singing Horse Studio Ltd.</a></li>
<li><a href="https://www.heroku.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-heroku.png);">Heroku</a></li>
<li><a href="https://www.galileo-press.de/" rel="nofollow" style="background-image:url(../../img/sponsors/2-galileo_press.png);">Galileo Press</a></li>
<li><a href="https://www.rheinwerk-verlag.de/" rel="nofollow" style="background-image:url(../../img/sponsors/2-rheinwerk_verlag.png);">Rheinwerk Verlag</a></li>
<li><a href="http://www.securitycompass.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-security_compass.png);">Security Compass</a></li>
<li><a href="https://www.djangoproject.com/foundation/" rel="nofollow" style="background-image:url(../../img/sponsors/2-django.png);">Django Software Foundation</a></li>
<li><a href="http://www.hipflaskapp.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-hipflask.png);">Hipflask</a></li>
Expand Down
38 changes: 38 additions & 0 deletions docs/topics/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@ You can determine your currently installed version using `pip freeze`:
## 3.0.x series


### 3.0.4

**Date**: [28th January 2015][3.0.4-milestone].

* Django 1.8a1 support. ([#2425][gh2425], [#2446][gh2446], [#2441][gh2441])
* Add `DictField` and support Django 1.8 `HStoreField`. ([#2451][gh2451], [#2106][gh2106])
* Add `UUIDField` and support Django 1.8 `UUIDField`. ([#2448][gh2448], [#2433][gh2433], [#2432][gh2432])
* `BaseRenderer.render` now raises `NotImplementedError`. ([#2434][gh2434])
* Fix timedelta JSON serialization on Python 2.6. ([#2430][gh2430])
* `ResultDict` and `ResultList` now appear as standard dict/list. ([#2421][gh2421])
* Fix visible `HiddenField` in the HTML form of the web browsable API page. ([#2410][gh2410])
* Use `OrderedDict` for `RelatedField.choices`. ([#2408][gh2408])
* Fix ident format when using `HTTP_X_FORWARDED_FOR`. ([#2401][gh2401])
* Fix invalid key with memcached while using throttling. ([#2400][gh2400])
* Fix `FileUploadParser` with version 3.x. ([#2399][gh2399])
* Fix the serializer inheritance. ([#2388][gh2388])
* Fix caching issues with `ReturnDict`. ([#2360][gh2360])

### 3.0.3

**Date**: [8th January 2015][3.0.3-milestone].
Expand Down Expand Up @@ -702,6 +720,7 @@ For older release notes, [please see the GitHub repo](old-release-notes).
[3.0.1-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.1+Release%22
[3.0.2-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.2+Release%22
[3.0.3-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.3+Release%22
[3.0.4-milestone]: https://github.com/tomchristie/django-rest-framework/issues?q=milestone%3A%223.0.4+Release%22

<!-- 3.0.1 -->
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
Expand Down Expand Up @@ -770,3 +789,22 @@ For older release notes, [please see the GitHub repo](old-release-notes).
[gh2355]: https://github.com/tomchristie/django-rest-framework/issues/2355
[gh2369]: https://github.com/tomchristie/django-rest-framework/issues/2369
[gh2386]: https://github.com/tomchristie/django-rest-framework/issues/2386
<!-- 3.0.4 -->
[gh2425]: https://github.com/tomchristie/django-rest-framework/issues/2425
[gh2446]: https://github.com/tomchristie/django-rest-framework/issues/2446
[gh2441]: https://github.com/tomchristie/django-rest-framework/issues/2441
[gh2451]: https://github.com/tomchristie/django-rest-framework/issues/2451
[gh2106]: https://github.com/tomchristie/django-rest-framework/issues/2106
[gh2448]: https://github.com/tomchristie/django-rest-framework/issues/2448
[gh2433]: https://github.com/tomchristie/django-rest-framework/issues/2433
[gh2432]: https://github.com/tomchristie/django-rest-framework/issues/2432
[gh2434]: https://github.com/tomchristie/django-rest-framework/issues/2434
[gh2430]: https://github.com/tomchristie/django-rest-framework/issues/2430
[gh2421]: https://github.com/tomchristie/django-rest-framework/issues/2421
[gh2410]: https://github.com/tomchristie/django-rest-framework/issues/2410
[gh2408]: https://github.com/tomchristie/django-rest-framework/issues/2408
[gh2401]: https://github.com/tomchristie/django-rest-framework/issues/2401
[gh2400]: https://github.com/tomchristie/django-rest-framework/issues/2400
[gh2399]: https://github.com/tomchristie/django-rest-framework/issues/2399
[gh2388]: https://github.com/tomchristie/django-rest-framework/issues/2388
[gh2360]: https://github.com/tomchristie/django-rest-framework/issues/2360
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ flake8==2.2.2
# Optional packages
markdown>=2.1.0
django-guardian==1.2.4
django-filter>=0.5.4
django-filter>=0.9.2

# wheel for PyPI installs
wheel==0.24.0
Expand Down
2 changes: 1 addition & 1 deletion rest_framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"""

__title__ = 'Django REST framework'
__version__ = '3.0.3'
__version__ = '3.0.4'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2015 Tom Christie'
Expand Down
2 changes: 1 addition & 1 deletion rest_framework/authtoken/south_migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def backwards(self, orm):
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
},
"%s.%s" % (User._meta.app_label, User._meta.module_name): {
'Meta': {'object_name': User._meta.module_name},
'Meta': {'object_name': User._meta.module_name, 'db_table': repr(User._meta.db_table)},
},
'authtoken.token': {
'Meta': {'object_name': 'Token'},
Expand Down
7 changes: 7 additions & 0 deletions rest_framework/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ def total_seconds(timedelta):
from django.http import HttpResponse as HttpResponseBase


# contrib.postgres only supported from 1.8 onwards.
try:
from django.contrib.postgres import fields as postgres_fields
except ImportError:
postgres_fields = None


# request only provides `resolver_match` from 1.5 onwards.
def get_resolver_match(request):
try:
Expand Down
77 changes: 75 additions & 2 deletions rest_framework/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import decimal
import inspect
import re
import uuid


class empty:
Expand Down Expand Up @@ -632,6 +633,23 @@ def __init__(self, **kwargs):
self.validators.append(validator)


class UUIDField(Field):
default_error_messages = {
'invalid': _('"{value}" is not a valid UUID.'),
}

def to_internal_value(self, data):
if not isinstance(data, uuid.UUID):
try:
return uuid.UUID(data)
except (ValueError, TypeError):
self.fail('invalid', value=data)
return data

def to_representation(self, value):
return str(value)


# Number types...

class IntegerField(Field):
Expand Down Expand Up @@ -1113,16 +1131,28 @@ def to_internal_value(self, data):

# Composite field types...

class _UnvalidatedField(Field):
def __init__(self, *args, **kwargs):
super(_UnvalidatedField, self).__init__(*args, **kwargs)
self.allow_blank = True
self.allow_null = True

def to_internal_value(self, data):
return data

def to_representation(self, value):
return value


class ListField(Field):
child = None
child = _UnvalidatedField()
initial = []
default_error_messages = {
'not_a_list': _('Expected a list of items but got type "{input_type}".')
}

def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
assert self.child is not None, '`child` is a required argument.'
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
super(ListField, self).__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)
Expand Down Expand Up @@ -1151,6 +1181,49 @@ def to_representation(self, data):
return [self.child.to_representation(item) for item in data]


class DictField(Field):
child = _UnvalidatedField()
initial = []
default_error_messages = {
'not_a_dict': _('Expected a dictionary of items but got type "{input_type}".')
}

def __init__(self, *args, **kwargs):
self.child = kwargs.pop('child', copy.deepcopy(self.child))
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
super(DictField, self).__init__(*args, **kwargs)
self.child.bind(field_name='', parent=self)

def get_value(self, dictionary):
# We override the default field access in order to support
# lists in HTML forms.
if html.is_html_input(dictionary):
return html.parse_html_list(dictionary, prefix=self.field_name)
return dictionary.get(self.field_name, empty)

def to_internal_value(self, data):
"""
Dicts of native values <- Dicts of primitive datatypes.
"""
if html.is_html_input(data):
data = html.parse_html_dict(data)
if not isinstance(data, dict):
self.fail('not_a_dict', input_type=type(data).__name__)
return dict([
(six.text_type(key), self.child.run_validation(value))
for key, value in data.items()
])

def to_representation(self, value):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
return dict([
(six.text_type(key), self.child.to_representation(val))
for key, val in value.items()
])


# Miscellaneous field types...

class ReadOnlyField(Field):
Expand Down
Loading

0 comments on commit 4ee4b4f

Please sign in to comment.