mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-29 04:54:00 +03:00
Merge master
This commit is contained in:
commit
4ee4b4f2dc
|
@ -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)`
|
**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
|
# Numeric fields
|
||||||
|
@ -320,7 +326,7 @@ Both the `allow_blank` and `allow_null` are valid options on `ChoiceField`, alth
|
||||||
|
|
||||||
## MultipleChoiceField
|
## 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)`
|
**Signature:** `MultipleChoiceField(choices)`
|
||||||
|
|
||||||
|
@ -374,7 +380,7 @@ A field class that validates a list of objects.
|
||||||
|
|
||||||
**Signature**: `ListField(child)`
|
**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:
|
For example, to validate a list of integers you might use something like the following:
|
||||||
|
|
||||||
|
@ -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.
|
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
|
# Miscellaneous fields
|
||||||
|
@ -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)`
|
**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:
|
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:
|
||||||
|
|
||||||
|
|
|
@ -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
|
[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]: https://github.com/alex/django-filter
|
||||||
[django-filter-docs]: https://django-filter.readthedocs.org/en/latest/index.html
|
[django-filter-docs]: https://django-filter.readthedocs.org/en/latest/index.html
|
||||||
[guardian]: http://pythonhosted.org/django-guardian/
|
[guardian]: https://django-guardian.readthedocs.org/
|
||||||
[view-permissions]: http://pythonhosted.org/django-guardian/userguide/assign.html
|
[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
|
[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
|
[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
|
[search-django-admin]: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
|
||||||
|
|
|
@ -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.
|
* `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
|
### Methods
|
||||||
|
|
||||||
**Base methods**:
|
**Base methods**:
|
||||||
|
|
||||||
#### `get_queryset(self)`
|
#### `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.
|
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.
|
||||||
|
|
||||||
|
@ -153,7 +149,7 @@ For example:
|
||||||
|
|
||||||
#### `get_serializer_class(self)`
|
#### `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.
|
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.
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,7 @@ If the view used with `FileUploadParser` is called with a `filename` URL keyword
|
||||||
def put(self, request, filename, format=None):
|
def put(self, request, filename, format=None):
|
||||||
file_obj = request.data['file']
|
file_obj = request.data['file']
|
||||||
# ...
|
# ...
|
||||||
# do some staff with uploaded file
|
# do some stuff with uploaded file
|
||||||
# ...
|
# ...
|
||||||
return Response(status=204)
|
return Response(status=204)
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ There are two mandatory arguments to the `register()` method:
|
||||||
|
|
||||||
Optionally, you may also specify an additional argument:
|
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:
|
The example above would generate the following URL patterns:
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ For example, you can append `router.urls` to a list of existing views…
|
||||||
router.register(r'accounts', AccountViewSet)
|
router.register(r'accounts', AccountViewSet)
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^forgot-password/$', ForgotPasswordFormView.as_view(),
|
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
|
||||||
]
|
]
|
||||||
|
|
||||||
urlpatterns += router.urls
|
urlpatterns += router.urls
|
||||||
|
@ -68,15 +68,15 @@ For example, you can append `router.urls` to a list of existing views…
|
||||||
Alternatively you can use Django's `include` function, like so…
|
Alternatively you can use Django's `include` function, like so…
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^forgot-password/$', ForgotPasswordFormView.as_view(),
|
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
|
||||||
url(r'^', include(router.urls))
|
url(r'^', include(router.urls)),
|
||||||
]
|
]
|
||||||
|
|
||||||
Router URL patterns can also be namespaces.
|
Router URL patterns can also be namespaces.
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^forgot-password/$', ForgotPasswordFormView.as_view(),
|
url(r'^forgot-password/$', ForgotPasswordFormView.as_view()),
|
||||||
url(r'^api/', include(router.urls, namespace='api'))
|
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.
|
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.
|
||||||
|
|
|
@ -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):
|
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'])
|
@detail_route(methods=['post', 'delete'])
|
||||||
def unset_password(self, request, pk=None):
|
def unset_password(self, request, pk=None):
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
BIN
docs/img/sponsors/2-rheinwerk_verlag.png
Normal file
BIN
docs/img/sponsors/2-rheinwerk_verlag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -55,7 +55,7 @@ REST framework requires the following:
|
||||||
The following packages are optional:
|
The following packages are optional:
|
||||||
|
|
||||||
* [Markdown][markdown] (2.1.0+) - Markdown support for the browsable API.
|
* [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.
|
* [django-guardian][django-guardian] (1.1.1+) - Object level permissions support.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
|
@ -665,7 +665,7 @@ This code *would be valid* in `2.4.3`:
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Account
|
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`
|
# Missing `queryset`
|
||||||
class AccountSerializer(serializers.Serializer):
|
class AccountSerializer(serializers.Serializer):
|
||||||
|
|
|
@ -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://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="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.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="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="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>
|
<li><a href="http://www.hipflaskapp.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-hipflask.png);">Hipflask</a></li>
|
||||||
|
|
|
@ -41,6 +41,24 @@ You can determine your currently installed version using `pip freeze`:
|
||||||
## 3.0.x series
|
## 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
|
### 3.0.3
|
||||||
|
|
||||||
**Date**: [8th January 2015][3.0.3-milestone].
|
**Date**: [8th January 2015][3.0.3-milestone].
|
||||||
|
@ -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.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.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.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 -->
|
<!-- 3.0.1 -->
|
||||||
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
|
[gh2013]: https://github.com/tomchristie/django-rest-framework/issues/2013
|
||||||
|
@ -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
|
[gh2355]: https://github.com/tomchristie/django-rest-framework/issues/2355
|
||||||
[gh2369]: https://github.com/tomchristie/django-rest-framework/issues/2369
|
[gh2369]: https://github.com/tomchristie/django-rest-framework/issues/2369
|
||||||
[gh2386]: https://github.com/tomchristie/django-rest-framework/issues/2386
|
[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
|
||||||
|
|
|
@ -10,7 +10,7 @@ flake8==2.2.2
|
||||||
# Optional packages
|
# Optional packages
|
||||||
markdown>=2.1.0
|
markdown>=2.1.0
|
||||||
django-guardian==1.2.4
|
django-guardian==1.2.4
|
||||||
django-filter>=0.5.4
|
django-filter>=0.9.2
|
||||||
|
|
||||||
# wheel for PyPI installs
|
# wheel for PyPI installs
|
||||||
wheel==0.24.0
|
wheel==0.24.0
|
||||||
|
|
|
@ -8,7 +8,7 @@ ______ _____ _____ _____ __
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__title__ = 'Django REST framework'
|
__title__ = 'Django REST framework'
|
||||||
__version__ = '3.0.3'
|
__version__ = '3.0.4'
|
||||||
__author__ = 'Tom Christie'
|
__author__ = 'Tom Christie'
|
||||||
__license__ = 'BSD 2-Clause'
|
__license__ = 'BSD 2-Clause'
|
||||||
__copyright__ = 'Copyright 2011-2015 Tom Christie'
|
__copyright__ = 'Copyright 2011-2015 Tom Christie'
|
||||||
|
|
|
@ -40,7 +40,7 @@ class Migration(SchemaMigration):
|
||||||
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
|
||||||
},
|
},
|
||||||
"%s.%s" % (User._meta.app_label, User._meta.module_name): {
|
"%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': {
|
'authtoken.token': {
|
||||||
'Meta': {'object_name': 'Token'},
|
'Meta': {'object_name': 'Token'},
|
||||||
|
|
|
@ -63,6 +63,13 @@ except ImportError:
|
||||||
from django.http import HttpResponse as HttpResponseBase
|
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.
|
# request only provides `resolver_match` from 1.5 onwards.
|
||||||
def get_resolver_match(request):
|
def get_resolver_match(request):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -23,6 +23,7 @@ import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
class empty:
|
class empty:
|
||||||
|
@ -632,6 +633,23 @@ class URLField(CharField):
|
||||||
self.validators.append(validator)
|
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...
|
# Number types...
|
||||||
|
|
||||||
class IntegerField(Field):
|
class IntegerField(Field):
|
||||||
|
@ -1113,8 +1131,21 @@ class ImageField(FileField):
|
||||||
|
|
||||||
# Composite field types...
|
# 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):
|
class ListField(Field):
|
||||||
child = None
|
child = _UnvalidatedField()
|
||||||
initial = []
|
initial = []
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'not_a_list': _('Expected a list of items but got type "{input_type}".')
|
'not_a_list': _('Expected a list of items but got type "{input_type}".')
|
||||||
|
@ -1122,7 +1153,6 @@ class ListField(Field):
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.child = kwargs.pop('child', copy.deepcopy(self.child))
|
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.'
|
assert not inspect.isclass(self.child), '`child` has not been instantiated.'
|
||||||
super(ListField, self).__init__(*args, **kwargs)
|
super(ListField, self).__init__(*args, **kwargs)
|
||||||
self.child.bind(field_name='', parent=self)
|
self.child.bind(field_name='', parent=self)
|
||||||
|
@ -1151,6 +1181,49 @@ class ListField(Field):
|
||||||
return [self.child.to_representation(item) for item in 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...
|
# Miscellaneous field types...
|
||||||
|
|
||||||
class ReadOnlyField(Field):
|
class ReadOnlyField(Field):
|
||||||
|
|
|
@ -152,7 +152,7 @@ class FileUploadParser(BaseParser):
|
||||||
None,
|
None,
|
||||||
encoding)
|
encoding)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
return DataAndFiles(None, {'file': result[1]})
|
return DataAndFiles({}, {'file': result[1]})
|
||||||
|
|
||||||
# This is the standard case.
|
# This is the standard case.
|
||||||
possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size]
|
possible_sizes = [x.chunk_size for x in upload_handlers if x.chunk_size]
|
||||||
|
|
|
@ -338,7 +338,12 @@ class ManyRelatedField(Field):
|
||||||
# We override the default field access in order to support
|
# We override the default field access in order to support
|
||||||
# lists in HTML forms.
|
# lists in HTML forms.
|
||||||
if html.is_html_input(dictionary):
|
if html.is_html_input(dictionary):
|
||||||
|
# Don't return [] if the update is partial
|
||||||
|
if self.field_name not in dictionary:
|
||||||
|
if getattr(self.root, 'partial', False):
|
||||||
|
return empty
|
||||||
return dictionary.getlist(self.field_name)
|
return dictionary.getlist(self.field_name)
|
||||||
|
|
||||||
return dictionary.get(self.field_name, empty)
|
return dictionary.get(self.field_name, empty)
|
||||||
|
|
||||||
def to_internal_value(self, data):
|
def to_internal_value(self, data):
|
||||||
|
|
|
@ -130,19 +130,13 @@ class SimpleRouter(BaseRouter):
|
||||||
If `base_name` is not specified, attempt to automatically determine
|
If `base_name` is not specified, attempt to automatically determine
|
||||||
it from the viewset.
|
it from the viewset.
|
||||||
"""
|
"""
|
||||||
# Note that `.model` attribute on views is deprecated, although we
|
|
||||||
# enforce the deprecation on the view `get_serializer_class()` and
|
|
||||||
# `get_queryset()` methods, rather than here.
|
|
||||||
model_cls = getattr(viewset, 'model', None)
|
|
||||||
queryset = getattr(viewset, 'queryset', None)
|
queryset = getattr(viewset, 'queryset', None)
|
||||||
if model_cls is None and queryset is not None:
|
|
||||||
model_cls = queryset.model
|
|
||||||
|
|
||||||
assert model_cls, '`base_name` argument not specified, and could ' \
|
assert queryset is not None, '`base_name` argument not specified, and could ' \
|
||||||
'not automatically determine the name from the viewset, as ' \
|
'not automatically determine the name from the viewset, as ' \
|
||||||
'it does not have a `.queryset` attribute.'
|
'it does not have a `.queryset` attribute.'
|
||||||
|
|
||||||
return model_cls._meta.object_name.lower()
|
return queryset.model._meta.object_name.lower()
|
||||||
|
|
||||||
def get_routes(self, viewset):
|
def get_routes(self, viewset):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -14,7 +14,7 @@ from __future__ import unicode_literals
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models.fields import FieldDoesNotExist, Field as DjangoModelField
|
from django.db.models.fields import FieldDoesNotExist, Field as DjangoModelField
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework.compat import unicode_to_repr
|
from rest_framework.compat import postgres_fields, unicode_to_repr
|
||||||
from rest_framework.utils import model_meta
|
from rest_framework.utils import model_meta
|
||||||
from rest_framework.utils.field_mapping import (
|
from rest_framework.utils.field_mapping import (
|
||||||
get_url_kwargs, get_field_kwargs,
|
get_url_kwargs, get_field_kwargs,
|
||||||
|
@ -1329,6 +1329,16 @@ class ModelSerializer(Serializer):
|
||||||
return validators
|
return validators
|
||||||
|
|
||||||
|
|
||||||
|
if hasattr(models, 'UUIDField'):
|
||||||
|
ModelSerializer._field_mapping[models.UUIDField] = UUIDField
|
||||||
|
|
||||||
|
if postgres_fields:
|
||||||
|
class CharMappingField(DictField):
|
||||||
|
child = CharField()
|
||||||
|
|
||||||
|
ModelSerializer._field_mapping[postgres_fields.HStoreField] = CharMappingField
|
||||||
|
|
||||||
|
|
||||||
class HyperlinkedModelSerializer(ModelSerializer):
|
class HyperlinkedModelSerializer(ModelSerializer):
|
||||||
"""
|
"""
|
||||||
A type of `ModelSerializer` that uses hyperlinked relationships instead
|
A type of `ModelSerializer` that uses hyperlinked relationships instead
|
||||||
|
|
|
@ -18,6 +18,7 @@ REST framework settings, checking for user settings first, then falling
|
||||||
back to the defaults.
|
back to the defaults.
|
||||||
"""
|
"""
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
from django.test.signals import setting_changed
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import importlib, six
|
from django.utils import importlib, six
|
||||||
from rest_framework import ISO_8601
|
from rest_framework import ISO_8601
|
||||||
|
@ -207,3 +208,13 @@ class APISettings(object):
|
||||||
|
|
||||||
|
|
||||||
api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS)
|
api_settings = APISettings(USER_SETTINGS, DEFAULTS, IMPORT_STRINGS)
|
||||||
|
|
||||||
|
|
||||||
|
def reload_api_settings(*args, **kwargs):
|
||||||
|
global api_settings
|
||||||
|
setting, value = kwargs['setting'], kwargs['value']
|
||||||
|
if setting == 'REST_FRAMEWORK':
|
||||||
|
api_settings = APISettings(value, DEFAULTS, IMPORT_STRINGS)
|
||||||
|
|
||||||
|
|
||||||
|
setting_changed.connect(reload_api_settings)
|
||||||
|
|
|
@ -38,6 +38,9 @@ class ClassLookupDict(object):
|
||||||
return self.mapping[cls]
|
return self.mapping[cls]
|
||||||
raise KeyError('Class %s not found in lookup.', cls.__name__)
|
raise KeyError('Class %s not found in lookup.', cls.__name__)
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.mapping[key] = value
|
||||||
|
|
||||||
|
|
||||||
def needs_label(model_field, field_name):
|
def needs_label(model_field, field_name):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -4,6 +4,7 @@ from rest_framework import serializers
|
||||||
import datetime
|
import datetime
|
||||||
import django
|
import django
|
||||||
import pytest
|
import pytest
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
# Tests for field keyword arguments and core functionality.
|
# Tests for field keyword arguments and core functionality.
|
||||||
|
@ -467,6 +468,23 @@ class TestURLField(FieldValues):
|
||||||
field = serializers.URLField()
|
field = serializers.URLField()
|
||||||
|
|
||||||
|
|
||||||
|
class TestUUIDField(FieldValues):
|
||||||
|
"""
|
||||||
|
Valid and invalid values for `UUIDField`.
|
||||||
|
"""
|
||||||
|
valid_inputs = {
|
||||||
|
'825d7aeb-05a9-45b5-a5b7-05df87923cda': uuid.UUID('825d7aeb-05a9-45b5-a5b7-05df87923cda'),
|
||||||
|
'825d7aeb05a945b5a5b705df87923cda': uuid.UUID('825d7aeb-05a9-45b5-a5b7-05df87923cda')
|
||||||
|
}
|
||||||
|
invalid_inputs = {
|
||||||
|
'825d7aeb-05a9-45b5-a5b7': ['"825d7aeb-05a9-45b5-a5b7" is not a valid UUID.']
|
||||||
|
}
|
||||||
|
outputs = {
|
||||||
|
uuid.UUID('825d7aeb-05a9-45b5-a5b7-05df87923cda'): '825d7aeb-05a9-45b5-a5b7-05df87923cda'
|
||||||
|
}
|
||||||
|
field = serializers.UUIDField()
|
||||||
|
|
||||||
|
|
||||||
# Number types...
|
# Number types...
|
||||||
|
|
||||||
class TestIntegerField(FieldValues):
|
class TestIntegerField(FieldValues):
|
||||||
|
@ -1029,7 +1047,7 @@ class TestValidImageField(FieldValues):
|
||||||
|
|
||||||
class TestListField(FieldValues):
|
class TestListField(FieldValues):
|
||||||
"""
|
"""
|
||||||
Values for `ListField`.
|
Values for `ListField` with IntegerField as child.
|
||||||
"""
|
"""
|
||||||
valid_inputs = [
|
valid_inputs = [
|
||||||
([1, 2, 3], [1, 2, 3]),
|
([1, 2, 3], [1, 2, 3]),
|
||||||
|
@ -1046,6 +1064,55 @@ class TestListField(FieldValues):
|
||||||
field = serializers.ListField(child=serializers.IntegerField())
|
field = serializers.ListField(child=serializers.IntegerField())
|
||||||
|
|
||||||
|
|
||||||
|
class TestUnvalidatedListField(FieldValues):
|
||||||
|
"""
|
||||||
|
Values for `ListField` with no `child` argument.
|
||||||
|
"""
|
||||||
|
valid_inputs = [
|
||||||
|
([1, '2', True, [4, 5, 6]], [1, '2', True, [4, 5, 6]]),
|
||||||
|
]
|
||||||
|
invalid_inputs = [
|
||||||
|
('not a list', ['Expected a list of items but got type "str".']),
|
||||||
|
]
|
||||||
|
outputs = [
|
||||||
|
([1, '2', True, [4, 5, 6]], [1, '2', True, [4, 5, 6]]),
|
||||||
|
]
|
||||||
|
field = serializers.ListField()
|
||||||
|
|
||||||
|
|
||||||
|
class TestDictField(FieldValues):
|
||||||
|
"""
|
||||||
|
Values for `ListField` with CharField as child.
|
||||||
|
"""
|
||||||
|
valid_inputs = [
|
||||||
|
({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
|
||||||
|
]
|
||||||
|
invalid_inputs = [
|
||||||
|
({'a': 1, 'b': None}, ['This field may not be null.']),
|
||||||
|
('not a dict', ['Expected a dictionary of items but got type "str".']),
|
||||||
|
]
|
||||||
|
outputs = [
|
||||||
|
({'a': 1, 'b': '2', 3: 3}, {'a': '1', 'b': '2', '3': '3'}),
|
||||||
|
]
|
||||||
|
field = serializers.DictField(child=serializers.CharField())
|
||||||
|
|
||||||
|
|
||||||
|
class TestUnvalidatedDictField(FieldValues):
|
||||||
|
"""
|
||||||
|
Values for `ListField` with no `child` argument.
|
||||||
|
"""
|
||||||
|
valid_inputs = [
|
||||||
|
({'a': 1, 'b': [4, 5, 6], 1: 123}, {'a': 1, 'b': [4, 5, 6], '1': 123}),
|
||||||
|
]
|
||||||
|
invalid_inputs = [
|
||||||
|
('not a dict', ['Expected a dictionary of items but got type "str".']),
|
||||||
|
]
|
||||||
|
outputs = [
|
||||||
|
({'a': 1, 'b': [4, 5, 6]}, {'a': 1, 'b': [4, 5, 6]}),
|
||||||
|
]
|
||||||
|
field = serializers.DictField()
|
||||||
|
|
||||||
|
|
||||||
# Tests for FieldField.
|
# Tests for FieldField.
|
||||||
# ---------------------
|
# ---------------------
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,15 @@ from django.db import models
|
||||||
from django.conf.urls import patterns, url
|
from django.conf.urls import patterns, url
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.test.utils import override_settings
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
from django.utils.dateparse import parse_date
|
from django.utils.dateparse import parse_date
|
||||||
|
from django.utils.six.moves import reload_module
|
||||||
from rest_framework import generics, serializers, status, filters
|
from rest_framework import generics, serializers, status, filters
|
||||||
from rest_framework.compat import django_filters
|
from rest_framework.compat import django_filters
|
||||||
from rest_framework.test import APIRequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
from .models import BaseFilterableItem, FilterableItem, BasicModel
|
from .models import BaseFilterableItem, FilterableItem, BasicModel
|
||||||
from .utils import temporary_setting
|
|
||||||
|
|
||||||
factory = APIRequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
@ -404,7 +406,9 @@ class SearchFilterTests(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_search_with_nonstandard_search_param(self):
|
def test_search_with_nonstandard_search_param(self):
|
||||||
with temporary_setting('SEARCH_PARAM', 'query', module=filters):
|
with override_settings(REST_FRAMEWORK={'SEARCH_PARAM': 'query'}):
|
||||||
|
reload_module(filters)
|
||||||
|
|
||||||
class SearchListView(generics.ListAPIView):
|
class SearchListView(generics.ListAPIView):
|
||||||
queryset = SearchFilterModel.objects.all()
|
queryset = SearchFilterModel.objects.all()
|
||||||
serializer_class = SearchFilterSerializer
|
serializer_class = SearchFilterSerializer
|
||||||
|
@ -422,6 +426,8 @@ class SearchFilterTests(TestCase):
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
reload_module(filters)
|
||||||
|
|
||||||
|
|
||||||
class OrderingFilterModel(models.Model):
|
class OrderingFilterModel(models.Model):
|
||||||
title = models.CharField(max_length=20)
|
title = models.CharField(max_length=20)
|
||||||
|
@ -642,7 +648,9 @@ class OrderingFilterTests(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_ordering_with_nonstandard_ordering_param(self):
|
def test_ordering_with_nonstandard_ordering_param(self):
|
||||||
with temporary_setting('ORDERING_PARAM', 'order', filters):
|
with override_settings(REST_FRAMEWORK={'ORDERING_PARAM': 'order'}):
|
||||||
|
reload_module(filters)
|
||||||
|
|
||||||
class OrderingListView(generics.ListAPIView):
|
class OrderingListView(generics.ListAPIView):
|
||||||
queryset = OrderingFilterModel.objects.all()
|
queryset = OrderingFilterModel.objects.all()
|
||||||
serializer_class = OrderingFilterSerializer
|
serializer_class = OrderingFilterSerializer
|
||||||
|
@ -662,6 +670,8 @@ class OrderingFilterTests(TestCase):
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
reload_module(filters)
|
||||||
|
|
||||||
|
|
||||||
class SensitiveOrderingFilterModel(models.Model):
|
class SensitiveOrderingFilterModel(models.Model):
|
||||||
username = models.CharField(max_length=20)
|
username = models.CharField(max_length=20)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
|
from .utils import mock_reverse, fail_reverse, BadType, MockObject, MockQueryset
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.utils.datastructures import MultiValueDict
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
from rest_framework.fields import empty
|
||||||
from rest_framework.test import APISimpleTestCase
|
from rest_framework.test import APISimpleTestCase
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -134,3 +136,34 @@ class TestSlugRelatedField(APISimpleTestCase):
|
||||||
def test_representation(self):
|
def test_representation(self):
|
||||||
representation = self.field.to_representation(self.instance)
|
representation = self.field.to_representation(self.instance)
|
||||||
assert representation == self.instance.name
|
assert representation == self.instance.name
|
||||||
|
|
||||||
|
|
||||||
|
class TestManyRelatedField(APISimpleTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.instance = MockObject(pk=1, name='foo')
|
||||||
|
self.field = serializers.StringRelatedField(many=True)
|
||||||
|
self.field.field_name = 'foo'
|
||||||
|
|
||||||
|
def test_get_value_regular_dictionary_full(self):
|
||||||
|
assert 'bar' == self.field.get_value({'foo': 'bar'})
|
||||||
|
assert empty == self.field.get_value({'baz': 'bar'})
|
||||||
|
|
||||||
|
def test_get_value_regular_dictionary_partial(self):
|
||||||
|
setattr(self.field.root, 'partial', True)
|
||||||
|
assert 'bar' == self.field.get_value({'foo': 'bar'})
|
||||||
|
assert empty == self.field.get_value({'baz': 'bar'})
|
||||||
|
|
||||||
|
def test_get_value_multi_dictionary_full(self):
|
||||||
|
mvd = MultiValueDict({'foo': ['bar1', 'bar2']})
|
||||||
|
assert ['bar1', 'bar2'] == self.field.get_value(mvd)
|
||||||
|
|
||||||
|
mvd = MultiValueDict({'baz': ['bar1', 'bar2']})
|
||||||
|
assert [] == self.field.get_value(mvd)
|
||||||
|
|
||||||
|
def test_get_value_multi_dictionary_partial(self):
|
||||||
|
setattr(self.field.root, 'partial', True)
|
||||||
|
mvd = MultiValueDict({'foo': ['bar1', 'bar2']})
|
||||||
|
assert ['bar1', 'bar2'] == self.field.get_value(mvd)
|
||||||
|
|
||||||
|
mvd = MultiValueDict({'baz': ['bar1', 'bar2']})
|
||||||
|
assert empty == self.field.get_value(mvd)
|
||||||
|
|
|
@ -180,7 +180,7 @@ class TestLookupValueRegex(TestCase):
|
||||||
class TestTrailingSlashIncluded(TestCase):
|
class TestTrailingSlashIncluded(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
class NoteViewSet(viewsets.ModelViewSet):
|
class NoteViewSet(viewsets.ModelViewSet):
|
||||||
model = RouterTestModel
|
queryset = RouterTestModel.objects.all()
|
||||||
|
|
||||||
self.router = SimpleRouter()
|
self.router = SimpleRouter()
|
||||||
self.router.register(r'notes', NoteViewSet)
|
self.router.register(r'notes', NoteViewSet)
|
||||||
|
@ -195,7 +195,7 @@ class TestTrailingSlashIncluded(TestCase):
|
||||||
class TestTrailingSlashRemoved(TestCase):
|
class TestTrailingSlashRemoved(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
class NoteViewSet(viewsets.ModelViewSet):
|
class NoteViewSet(viewsets.ModelViewSet):
|
||||||
model = RouterTestModel
|
queryset = RouterTestModel.objects.all()
|
||||||
|
|
||||||
self.router = SimpleRouter(trailing_slash=False)
|
self.router = SimpleRouter(trailing_slash=False)
|
||||||
self.router.register(r'notes', NoteViewSet)
|
self.router.register(r'notes', NoteViewSet)
|
||||||
|
@ -210,7 +210,8 @@ class TestTrailingSlashRemoved(TestCase):
|
||||||
class TestNameableRoot(TestCase):
|
class TestNameableRoot(TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
class NoteViewSet(viewsets.ModelViewSet):
|
class NoteViewSet(viewsets.ModelViewSet):
|
||||||
model = RouterTestModel
|
queryset = RouterTestModel.objects.all()
|
||||||
|
|
||||||
self.router = DefaultRouter()
|
self.router = DefaultRouter()
|
||||||
self.router.root_view_name = 'nameable-root'
|
self.router.root_view_name = 'nameable-root'
|
||||||
self.router.register(r'notes', NoteViewSet)
|
self.router.register(r'notes', NoteViewSet)
|
||||||
|
|
|
@ -1,30 +1,5 @@
|
||||||
from contextlib import contextmanager
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.core.urlresolvers import NoReverseMatch
|
from django.core.urlresolvers import NoReverseMatch
|
||||||
from django.utils import six
|
|
||||||
from rest_framework.settings import api_settings
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def temporary_setting(setting, value, module=None):
|
|
||||||
"""
|
|
||||||
Temporarily change value of setting for test.
|
|
||||||
|
|
||||||
Optionally reload given module, useful when module uses value of setting on
|
|
||||||
import.
|
|
||||||
"""
|
|
||||||
original_value = getattr(api_settings, setting)
|
|
||||||
setattr(api_settings, setting, value)
|
|
||||||
|
|
||||||
if module is not None:
|
|
||||||
six.moves.reload_module(module)
|
|
||||||
|
|
||||||
yield
|
|
||||||
|
|
||||||
setattr(api_settings, setting, original_value)
|
|
||||||
|
|
||||||
if module is not None:
|
|
||||||
six.moves.reload_module(module)
|
|
||||||
|
|
||||||
|
|
||||||
class MockObject(object):
|
class MockObject(object):
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -18,7 +18,7 @@ deps =
|
||||||
djangomaster: https://github.com/django/django/zipball/master
|
djangomaster: https://github.com/django/django/zipball/master
|
||||||
django-guardian==1.2.4
|
django-guardian==1.2.4
|
||||||
pytest-django==2.8.0
|
pytest-django==2.8.0
|
||||||
{py26,py27,py32,py33,py34}-django{14,15,16,17}: django-filter==0.9.1
|
django-filter==0.9.2
|
||||||
markdown>=2.1.0
|
markdown>=2.1.0
|
||||||
|
|
||||||
[testenv:py27-flake8]
|
[testenv:py27-flake8]
|
||||||
|
|
Loading…
Reference in New Issue
Block a user