mirror of
https://github.com/encode/django-rest-framework.git
synced 2024-11-23 18:13:57 +03:00
commit
c058ab36b1
|
@ -121,7 +121,7 @@ To use the `TokenAuthentication` scheme, include `rest_framework.authtoken` in y
|
||||||
'rest_framework.authtoken'
|
'rest_framework.authtoken'
|
||||||
)
|
)
|
||||||
|
|
||||||
Make sure to run `manage.py syncdb` after changing your settings.
|
Make sure to run `manage.py syncdb` after changing your settings. The `authtoken` database tables are managed by south (see [Schema migrations](#schema-migrations) below).
|
||||||
|
|
||||||
You'll also need to create tokens for your users.
|
You'll also need to create tokens for your users.
|
||||||
|
|
||||||
|
@ -184,9 +184,11 @@ The `obtain_auth_token` view will return a JSON response when valid `username` a
|
||||||
|
|
||||||
Note that the default `obtain_auth_token` view explicitly uses JSON requests and responses, rather than using default renderer and parser classes in your settings. If you need a customized version of the `obtain_auth_token` view, you can do so by overriding the `ObtainAuthToken` view class, and using that in your url conf instead.
|
Note that the default `obtain_auth_token` view explicitly uses JSON requests and responses, rather than using default renderer and parser classes in your settings. If you need a customized version of the `obtain_auth_token` view, you can do so by overriding the `ObtainAuthToken` view class, and using that in your url conf instead.
|
||||||
|
|
||||||
#### Custom user models
|
#### Schema migrations
|
||||||
|
|
||||||
The `rest_framework.authtoken` app includes a south migration that will create the authtoken table. If you're using a [custom user model][custom-user-model] you'll need to make sure that any initial migration that creates the user table runs before the authtoken table is created.
|
The `rest_framework.authtoken` app includes a south migration that will create the authtoken table.
|
||||||
|
|
||||||
|
If you're using a [custom user model][custom-user-model] you'll need to make sure that any initial migration that creates the user table runs before the authtoken table is created.
|
||||||
|
|
||||||
You can do so by inserting a `needed_by` attribute in your user migration:
|
You can do so by inserting a `needed_by` attribute in your user migration:
|
||||||
|
|
||||||
|
@ -201,6 +203,12 @@ You can do so by inserting a `needed_by` attribute in your user migration:
|
||||||
|
|
||||||
For more details, see the [south documentation on dependencies][south-dependencies].
|
For more details, see the [south documentation on dependencies][south-dependencies].
|
||||||
|
|
||||||
|
Also note that if you're using a `post_save` signal to create tokens, then the first time you create the database tables, you'll need to ensure any migrations are run prior to creating any superusers. For example:
|
||||||
|
|
||||||
|
python manage.py syncdb --noinput # Won't create a superuser just yet, due to `--noinput`.
|
||||||
|
python manage.py migrate
|
||||||
|
python manage.py createsuperuser
|
||||||
|
|
||||||
## SessionAuthentication
|
## SessionAuthentication
|
||||||
|
|
||||||
This authentication scheme uses Django's default session backend for authentication. Session authentication is appropriate for AJAX clients that are running in the same session context as your website.
|
This authentication scheme uses Django's default session backend for authentication. Session authentication is appropriate for AJAX clients that are running in the same session context as your website.
|
||||||
|
|
|
@ -43,7 +43,11 @@ This is a valid approach as the HTTP spec deliberately underspecifies how a serv
|
||||||
|
|
||||||
It's unlikely that you'll want to provide a custom content negotiation scheme for REST framework, but you can do so if needed. To implement a custom content negotiation scheme override `BaseContentNegotiation`.
|
It's unlikely that you'll want to provide a custom content negotiation scheme for REST framework, but you can do so if needed. To implement a custom content negotiation scheme override `BaseContentNegotiation`.
|
||||||
|
|
||||||
REST framework's content negotiation classes handle selection of both the appropriate parser for the request, and the appropriate renderer for the response, so you should implement both the `.select_parser(request, parsers)` and `.select_renderer(request, renderers, format_suffix)` methods.
|
REST framework's content negotiation classes handle selection of both the appropriate parser for the request, and the appropriate renderer for the response, so you should implement both the `.select_parser(request, parsers)` and `.select_renderer(request, renderers, format_suffix)` methods.
|
||||||
|
|
||||||
|
The `select_parser()` method should return one of the parser instances from the list of available parsers, or `None` if none of the parsers can handle the incoming request.
|
||||||
|
|
||||||
|
The `select_renderer()` method should return a two-tuple of (renderer instance, media type), or raise a `NotAcceptable` exception.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
|
@ -61,6 +65,27 @@ request when selecting the appropriate parser or renderer.
|
||||||
"""
|
"""
|
||||||
Select the first renderer in the `.renderer_classes` list.
|
Select the first renderer in the `.renderer_classes` list.
|
||||||
"""
|
"""
|
||||||
return renderers[0]
|
return (renderers[0], renderers[0].media_type)
|
||||||
|
|
||||||
|
## Setting the content negotiation
|
||||||
|
|
||||||
|
The default content negotiation class may be set globally, using the `DEFAULT_CONTENT_NEGOTIATION_CLASS` setting. For example, the following settings would use our example `IgnoreClientContentNegotiation` class.
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'myapp.negotiation.IgnoreClientContentNegotiation',
|
||||||
|
}
|
||||||
|
|
||||||
|
You can also set the content negotiation used for an individual view, or viewset, using the `APIView` class based views.
|
||||||
|
|
||||||
|
class NoNegotiationView(APIView):
|
||||||
|
"""
|
||||||
|
An example view that does not perform content negotiation.
|
||||||
|
"""
|
||||||
|
content_negotiation_class = IgnoreClientContentNegotiation
|
||||||
|
|
||||||
|
def get(self, request, format=None):
|
||||||
|
return Response({
|
||||||
|
'accepted media type': request.accepted_renderer.media_type
|
||||||
|
})
|
||||||
|
|
||||||
[accept-header]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
[accept-header]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
||||||
|
|
|
@ -217,6 +217,24 @@ Renders data into HTML for the Browsable API. This renderer will determine whic
|
||||||
|
|
||||||
**.charset**: `utf-8`
|
**.charset**: `utf-8`
|
||||||
|
|
||||||
|
#### Customizing BrowsableAPIRenderer
|
||||||
|
|
||||||
|
By default the response content will be rendered with the highest priority renderer apart from `BrowseableAPIRenderer`. If you need to customize this behavior, for example to use HTML as the default return format, but use JSON in the browsable API, you can do so by overriding the `get_default_renderer()` method. For example:
|
||||||
|
|
||||||
|
class CustomBrowsableAPIRenderer(BrowsableAPIRenderer):
|
||||||
|
def get_default_renderer(self, view):
|
||||||
|
return JSONRenderer()
|
||||||
|
|
||||||
|
## MultiPartRenderer
|
||||||
|
|
||||||
|
This renderer is used for rendering HTML multipart form data. **It is not suitable as a response renderer**, but is instead used for creating test requests, using REST framework's [test client and test request factory][testing].
|
||||||
|
|
||||||
|
**.media_type**: `multipart/form-data; boundary=BoUnDaRyStRiNg`
|
||||||
|
|
||||||
|
**.format**: `'.multipart'`
|
||||||
|
|
||||||
|
**.charset**: `utf-8`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Custom renderers
|
# Custom renderers
|
||||||
|
@ -373,6 +391,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
|
||||||
[rfc4627]: http://www.ietf.org/rfc/rfc4627.txt
|
[rfc4627]: http://www.ietf.org/rfc/rfc4627.txt
|
||||||
[cors]: http://www.w3.org/TR/cors/
|
[cors]: http://www.w3.org/TR/cors/
|
||||||
[cors-docs]: ../topics/ajax-csrf-cors.md
|
[cors-docs]: ../topics/ajax-csrf-cors.md
|
||||||
|
[testing]: testing.md
|
||||||
[HATEOAS]: http://timelessrepo.com/haters-gonna-hateoas
|
[HATEOAS]: http://timelessrepo.com/haters-gonna-hateoas
|
||||||
[quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
|
[quote]: http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
|
||||||
[application/vnd.github+json]: http://developer.github.com/v3/media/
|
[application/vnd.github+json]: http://developer.github.com/v3/media/
|
||||||
|
|
|
@ -308,6 +308,12 @@ By default, all the model fields on the class will be mapped to corresponding se
|
||||||
|
|
||||||
Any relationships such as foreign keys on the model will be mapped to `PrimaryKeyRelatedField`. Other models fields will be mapped to a corresponding serializer field.
|
Any relationships such as foreign keys on the model will be mapped to `PrimaryKeyRelatedField`. Other models fields will be mapped to a corresponding serializer field.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note**: When validation is applied to a `ModelSerializer`, both the serializer fields, and their corresponding model fields must correctly validate. If you have optional fields on your model, make sure to correctly set `blank=True` on the model field, as well as setting `required=False` on the serializer field.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Specifying which fields should be included
|
## Specifying which fields should be included
|
||||||
|
|
||||||
If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`.
|
If you only want a subset of the default fields to be used in a model serializer, you can do so using `fields` or `exclude` options, just as you would with a `ModelForm`.
|
||||||
|
@ -423,6 +429,49 @@ You can create customized subclasses of `ModelSerializer` or `HyperlinkedModelSe
|
||||||
|
|
||||||
Doing so should be considered advanced usage, and will only be needed if you have some particular serializer requirements that you often need to repeat.
|
Doing so should be considered advanced usage, and will only be needed if you have some particular serializer requirements that you often need to repeat.
|
||||||
|
|
||||||
|
## Dynamically modifiying fields
|
||||||
|
|
||||||
|
Once a serializer has been initialized, the dictionary of fields that are set on the serializer may be accessed using the `.fields` attribute. Accessing and modifying this attribute allows you to dynamically modify the serializer.
|
||||||
|
|
||||||
|
Modifying the `fields` argument directly allows you to do interesting things such as changing the arguments on serializer fields at runtime, rather than at the point of declaring the serializer.
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
For example, if you wanted to be able to set which fields should be used by a serializer at the point of initializing it, you could create a serializer class like so:
|
||||||
|
|
||||||
|
class DynamicFieldsModelSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
A ModelSerializer that takes an additional `fields` argument that
|
||||||
|
controls which fields should be displayed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# Don't pass the 'fields' arg up to the superclass
|
||||||
|
fields = kwargs.pop('fields', None)
|
||||||
|
|
||||||
|
# Instatiate the superclass normally
|
||||||
|
super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if fields:
|
||||||
|
# Drop any fields that are not specified in the `fields` argument.
|
||||||
|
allowed = set(fields)
|
||||||
|
existing = set(self.fields.keys())
|
||||||
|
for field_name in existing - allowed:
|
||||||
|
self.fields.pop(field_name)
|
||||||
|
|
||||||
|
This would then allow you to do the following:
|
||||||
|
|
||||||
|
>>> class UserSerializer(DynamicFieldsModelSerializer):
|
||||||
|
>>> class Meta:
|
||||||
|
>>> model = User
|
||||||
|
>>> fields = ('id', 'username', 'email')
|
||||||
|
>>>
|
||||||
|
>>> print UserSerializer(user)
|
||||||
|
{'id': 2, 'username': 'jonwatts', 'email': 'jon@example.com'}
|
||||||
|
>>>
|
||||||
|
>>> print UserSerializer(user, fields=('id', 'email'))
|
||||||
|
{'id': 2, 'email': 'jon@example.com'}
|
||||||
|
|
||||||
## Customising the default fields
|
## Customising the default fields
|
||||||
|
|
||||||
The `field_mapping` attribute is a dictionary that maps model classes to serializer classes. Overriding the attribute will let you set a different set of default serializer classes.
|
The `field_mapping` attribute is a dictionary that maps model classes to serializer classes. Overriding the attribute will let you set a different set of default serializer classes.
|
||||||
|
@ -457,7 +506,7 @@ Note that the `model_field` argument will be `None` for reverse relationships.
|
||||||
|
|
||||||
Returns the field instance that should be used for non-relational, non-pk fields.
|
Returns the field instance that should be used for non-relational, non-pk fields.
|
||||||
|
|
||||||
## Example
|
### Example
|
||||||
|
|
||||||
The following custom model serializer could be used as a base class for model serializers that should always exclude the pk by default.
|
The following custom model serializer could be used as a base class for model serializers that should always exclude the pk by default.
|
||||||
|
|
||||||
|
|
|
@ -149,6 +149,33 @@ Default: `None`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Test settings
|
||||||
|
|
||||||
|
*The following settings control the behavior of APIRequestFactory and APIClient*
|
||||||
|
|
||||||
|
#### TEST_REQUEST_DEFAULT_FORMAT
|
||||||
|
|
||||||
|
The default format that should be used when making test requests.
|
||||||
|
|
||||||
|
This should match up with the format of one of the renderer classes in the `TEST_REQUEST_RENDERER_CLASSES` setting.
|
||||||
|
|
||||||
|
Default: `'multipart'`
|
||||||
|
|
||||||
|
#### TEST_REQUEST_RENDERER_CLASSES
|
||||||
|
|
||||||
|
The renderer classes that are supported when building test requests.
|
||||||
|
|
||||||
|
The format of any of these renderer classes may be used when contructing a test request, for example: `client.post('/users', {'username': 'jamie'}, format='json')`
|
||||||
|
|
||||||
|
Default:
|
||||||
|
|
||||||
|
(
|
||||||
|
'rest_framework.renderers.MultiPartRenderer',
|
||||||
|
'rest_framework.renderers.JSONRenderer'
|
||||||
|
)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Browser overrides
|
## Browser overrides
|
||||||
|
|
||||||
*The following settings provide URL or form-based overrides of the default browser behavior.*
|
*The following settings provide URL or form-based overrides of the default browser behavior.*
|
||||||
|
|
257
docs/api-guide/testing.md
Normal file
257
docs/api-guide/testing.md
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
<a class="github" href="test.py"></a>
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
|
||||||
|
> Code without tests is broken as designed
|
||||||
|
>
|
||||||
|
> — [Jacob Kaplan-Moss][cite]
|
||||||
|
|
||||||
|
REST framework includes a few helper classes that extend Django's existing test framework, and improve support for making API requests.
|
||||||
|
|
||||||
|
# APIRequestFactory
|
||||||
|
|
||||||
|
Extends [Django's existing `RequestFactory` class][requestfactory].
|
||||||
|
|
||||||
|
## Creating test requests
|
||||||
|
|
||||||
|
The `APIRequestFactory` class supports an almost identical API to Django's standard `RequestFactory` class. This means the that standard `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` and `.options()` methods are all available.
|
||||||
|
|
||||||
|
# Using the standard RequestFactory API to create a form POST request
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
request = factory.post('/notes/', {'title': 'new idea'})
|
||||||
|
|
||||||
|
#### Using the `format` argument
|
||||||
|
|
||||||
|
Methods which create a request body, such as `post`, `put` and `patch`, include a `format` argument, which make it easy to generate requests using a content type other than multipart form data. For example:
|
||||||
|
|
||||||
|
# Create a JSON POST request
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
request = factory.post('/notes/', {'title': 'new idea'}, format='json')
|
||||||
|
|
||||||
|
By default the available formats are `'multipart'` and `'json'`. For compatibility with Django's existing `RequestFactory` the default format is `'multipart'`.
|
||||||
|
|
||||||
|
To support a wider set of request formats, or change the default format, [see the configuration section][configuration].
|
||||||
|
|
||||||
|
#### Explicitly encoding the request body
|
||||||
|
|
||||||
|
If you need to explictly encode the request body, you can do so by setting the `content_type` flag. For example:
|
||||||
|
|
||||||
|
request = factory.post('/notes/', json.dumps({'title': 'new idea'}), content_type='application/json')
|
||||||
|
|
||||||
|
#### PUT and PATCH with form data
|
||||||
|
|
||||||
|
One difference worth noting between Django's `RequestFactory` and REST framework's `APIRequestFactory` is that multipart form data will be encoded for methods other than just `.post()`.
|
||||||
|
|
||||||
|
For example, using `APIRequestFactory`, you can make a form PUT request like so:
|
||||||
|
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
request = factory.put('/notes/547/', {'title': 'remember to email dave'})
|
||||||
|
|
||||||
|
Using Django's `RequestFactory`, you'd need to explicitly encode the data yourself:
|
||||||
|
|
||||||
|
factory = RequestFactory()
|
||||||
|
data = {'title': 'remember to email dave'}
|
||||||
|
content = encode_multipart('BoUnDaRyStRiNg', data)
|
||||||
|
content_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'
|
||||||
|
request = factory.put('/notes/547/', content, content_type=content_type)
|
||||||
|
|
||||||
|
## Forcing authentication
|
||||||
|
|
||||||
|
When testing views directly using a request factory, it's often convenient to be able to directly authenticate the request, rather than having to construct the correct authentication credentials.
|
||||||
|
|
||||||
|
To forcibly authenticate a request, use the `force_authenticate()` method.
|
||||||
|
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
user = User.objects.get(username='olivia')
|
||||||
|
view = AccountDetail.as_view()
|
||||||
|
|
||||||
|
# Make an authenticated request to the view...
|
||||||
|
request = factory.get('/accounts/django-superstars/')
|
||||||
|
force_authenticate(request, user=user)
|
||||||
|
response = view(request)
|
||||||
|
|
||||||
|
The signature for the method is `force_authenticate(request, user=None, token=None)`. When making the call, either or both of the user and token may be set.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note**: When using `APIRequestFactory`, the object that is returned is Django's standard `HttpRequest`, and not REST framework's `Request` object, which is only generated once the view is called.
|
||||||
|
|
||||||
|
This means that setting attributes directly on the request object may not always have the effect you expect. For example, setting `.token` directly will have no effect, and setting `.user` directly will only work if session authentication is being used.
|
||||||
|
|
||||||
|
# Request will only authenticate if `SessionAuthentication` is in use.
|
||||||
|
request = factory.get('/accounts/django-superstars/')
|
||||||
|
request.user = user
|
||||||
|
response = view(request)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Forcing CSRF validation
|
||||||
|
|
||||||
|
By default, requests created with `APIRequestFactory` will not have CSRF validation applied when passed to a REST framework view. If you need to explicitly turn CSRF validation on, you can do so by setting the `enforce_csrf_checks` flag when instantiating the factory.
|
||||||
|
|
||||||
|
factory = APIRequestFactory(enforce_csrf_checks=True)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Note**: It's worth noting that Django's standard `RequestFactory` doesn't need to include this option, because when using regular Django the CSRF validation takes place in middleware, which is not run when testing views directly. When using REST framework, CSRF validation takes place inside the view, so the request factory needs to disable view-level CSRF checks.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# APIClient
|
||||||
|
|
||||||
|
Extends [Django's existing `Client` class][client].
|
||||||
|
|
||||||
|
## Making requests
|
||||||
|
|
||||||
|
The `APIClient` class supports the same request interface as `APIRequestFactory`. This means the that standard `.get()`, `.post()`, `.put()`, `.patch()`, `.delete()`, `.head()` and `.options()` methods are all available. For example:
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.post('/notes/', {'title': 'new idea'}, format='json')
|
||||||
|
|
||||||
|
To support a wider set of request formats, or change the default format, [see the configuration section][configuration].
|
||||||
|
|
||||||
|
## Authenticating
|
||||||
|
|
||||||
|
#### .login(**kwargs)
|
||||||
|
|
||||||
|
The `login` method functions exactly as it does with Django's regular `Client` class. This allows you to authenticate requests against any views which include `SessionAuthentication`.
|
||||||
|
|
||||||
|
# Make all requests in the context of a logged in session.
|
||||||
|
client = APIClient()
|
||||||
|
client.login(username='lauren', password='secret')
|
||||||
|
|
||||||
|
To logout, call the `logout` method as usual.
|
||||||
|
|
||||||
|
# Log out
|
||||||
|
client.logout()
|
||||||
|
|
||||||
|
The `login` method is appropriate for testing APIs that use session authentication, for example web sites which include AJAX interaction with the API.
|
||||||
|
|
||||||
|
#### .credentials(**kwargs)
|
||||||
|
|
||||||
|
The `credentials` method can be used to set headers that will then be included on all subsequent requests by the test client.
|
||||||
|
|
||||||
|
# Include an appropriate `Authorization:` header on all requests.
|
||||||
|
token = Token.objects.get(username='lauren')
|
||||||
|
client = APIClient()
|
||||||
|
client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
|
||||||
|
|
||||||
|
Note that calling `credentials` a second time overwrites any existing credentials. You can unset any existing credentials by calling the method with no arguments.
|
||||||
|
|
||||||
|
# Stop including any credentials
|
||||||
|
client.credentials()
|
||||||
|
|
||||||
|
The `credentials` method is appropriate for testing APIs that require authentication headers, such as basic authentication, OAuth1a and OAuth2 authentication, and simple token authentication schemes.
|
||||||
|
|
||||||
|
#### .force_authenticate(user=None, token=None)
|
||||||
|
|
||||||
|
Sometimes you may want to bypass authentication, and simple force all requests by the test client to be automatically treated as authenticated.
|
||||||
|
|
||||||
|
This can be a useful shortcut if you're testing the API but don't want to have to construct valid authentication credentials in order to make test requests.
|
||||||
|
|
||||||
|
user = User.objects.get(username='lauren')
|
||||||
|
client = APIClient()
|
||||||
|
client.force_authenticate(user=user)
|
||||||
|
|
||||||
|
To unauthenticate subsequent requests, call `force_authenticate` setting the user and/or token to `None`.
|
||||||
|
|
||||||
|
client.force_authenticate(user=None)
|
||||||
|
|
||||||
|
## CSRF validation
|
||||||
|
|
||||||
|
By default CSRF validation is not applied when using `APIClient`. If you need to explicitly enable CSRF validation, you can do so by setting the `enforce_csrf_checks` flag when instantiating the client.
|
||||||
|
|
||||||
|
client = APIClient(enforce_csrf_checks=True)
|
||||||
|
|
||||||
|
As usual CSRF validation will only apply to any session authenticated views. This means CSRF validation will only occur if the client has been logged in by calling `login()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Test cases
|
||||||
|
|
||||||
|
REST framework includes the following test case classes, that mirror the existing Django test case classes, but use `APIClient` instead of Django's default `Client`.
|
||||||
|
|
||||||
|
* `APISimpleTestCase`
|
||||||
|
* `APITransactionTestCase`
|
||||||
|
* `APITestCase`
|
||||||
|
* `APILiveServerTestCase`
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
You can use any of REST framework's test case classes as you would for the regular Django test case classes. The `self.client` attribute will be an `APIClient` instance.
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from rest_framework import status
|
||||||
|
from rest_framework.test import APITestCase
|
||||||
|
|
||||||
|
class AccountTests(APITestCase):
|
||||||
|
def test_create_account(self):
|
||||||
|
"""
|
||||||
|
Ensure we can create a new account object.
|
||||||
|
"""
|
||||||
|
url = reverse('account-list')
|
||||||
|
data = {'name': 'DabApps'}
|
||||||
|
response = self.client.post(url, data, format='json')
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
self.assertEqual(response.data, data)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Testing responses
|
||||||
|
|
||||||
|
## Checking the response data
|
||||||
|
|
||||||
|
When checking the validity of test responses it's often more convenient to inspect the data that the response was created with, rather than inspecting the fully rendered response.
|
||||||
|
|
||||||
|
For example, it's easier to inspect `request.data`:
|
||||||
|
|
||||||
|
response = self.client.get('/users/4/')
|
||||||
|
self.assertEqual(response.data, {'id': 4, 'username': 'lauren'})
|
||||||
|
|
||||||
|
Instead of inspecting the result of parsing `request.content`:
|
||||||
|
|
||||||
|
response = self.client.get('/users/4/')
|
||||||
|
self.assertEqual(json.loads(response.content), {'id': 4, 'username': 'lauren'})
|
||||||
|
|
||||||
|
## Rendering responses
|
||||||
|
|
||||||
|
If you're testing views directly using `APIRequestFactory`, the responses that are returned will not yet be rendered, as rendering of template responses is performed by Django's internal request-response cycle. In order to access `response.content`, you'll first need to render the response.
|
||||||
|
|
||||||
|
view = UserDetail.as_view()
|
||||||
|
request = factory.get('/users/4')
|
||||||
|
response = view(request, pk='4')
|
||||||
|
response.render() # Cannot access `response.content` without this.
|
||||||
|
self.assertEqual(response.content, '{"username": "lauren", "id": 4}')
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
|
||||||
|
## Setting the default format
|
||||||
|
|
||||||
|
The default format used to make test requests may be set using the `TEST_REQUEST_DEFAULT_FORMAT` setting key. For example, to always use JSON for test requests by default instead of standard multipart form requests, set the following in your `settings.py` file:
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
...
|
||||||
|
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
|
||||||
|
}
|
||||||
|
|
||||||
|
## Setting the available formats
|
||||||
|
|
||||||
|
If you need to test requests using something other than multipart or json requests, you can do so by setting the `TEST_REQUEST_RENDERER_CLASSES` setting.
|
||||||
|
|
||||||
|
For example, to add support for using `format='yaml'` in test requests, you might have something like this in your `settings.py` file.
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
...
|
||||||
|
'TEST_REQUEST_RENDERER_CLASSES': (
|
||||||
|
'rest_framework.renderers.MultiPartRenderer',
|
||||||
|
'rest_framework.renderers.JSONRenderer',
|
||||||
|
'rest_framework.renderers.YAMLRenderer'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
[cite]: http://jacobian.org/writing/django-apps-with-buildout/#s-create-a-test-wrapper
|
||||||
|
[client]: https://docs.djangoproject.com/en/dev/topics/testing/overview/#module-django.test.client
|
||||||
|
[requestfactory]: https://docs.djangoproject.com/en/dev/topics/testing/advanced/#django.test.client.RequestFactory
|
||||||
|
[configuration]: #configuration
|
|
@ -12,7 +12,7 @@ As with permissions, multiple throttles may be used. Your API might have a rest
|
||||||
|
|
||||||
Another scenario where you might want to use multiple throttles would be if you need to impose different constraints on different parts of the API, due to some services being particularly resource-intensive.
|
Another scenario where you might want to use multiple throttles would be if you need to impose different constraints on different parts of the API, due to some services being particularly resource-intensive.
|
||||||
|
|
||||||
Multiple throttles can also be used if you want to impose both burst throttling rates, and sustained throttling rates. For example, you might want to limit a user to a maximum of 60 requests per minute, and 1000 requests per day.
|
Multiple throttles can also be used if you want to impose both burst throttling rates, and sustained throttling rates. For example, you might want to limit a user to a maximum of 60 requests per minute, and 1000 requests per day.
|
||||||
|
|
||||||
Throttles do not necessarily only refer to rate-limiting requests. For example a storage service might also need to throttle against bandwidth, and a paid data service might want to throttle against a certain number of a records being accessed.
|
Throttles do not necessarily only refer to rate-limiting requests. For example a storage service might also need to throttle against bandwidth, and a paid data service might want to throttle against a certain number of a records being accessed.
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ You can also set the throttling policy on a per-view or per-viewset basis,
|
||||||
using the `APIView` class based views.
|
using the `APIView` class based views.
|
||||||
|
|
||||||
class ExampleView(APIView):
|
class ExampleView(APIView):
|
||||||
throttle_classes = (UserThrottle,)
|
throttle_classes = (UserRateThrottle,)
|
||||||
|
|
||||||
def get(self, request, format=None):
|
def get(self, request, format=None):
|
||||||
content = {
|
content = {
|
||||||
|
@ -55,7 +55,7 @@ using the `APIView` class based views.
|
||||||
Or, if you're using the `@api_view` decorator with function based views.
|
Or, if you're using the `@api_view` decorator with function based views.
|
||||||
|
|
||||||
@api_view('GET')
|
@api_view('GET')
|
||||||
@throttle_classes(UserThrottle)
|
@throttle_classes(UserRateThrottle)
|
||||||
def example_view(request, format=None):
|
def example_view(request, format=None):
|
||||||
content = {
|
content = {
|
||||||
'status': 'request was permitted'
|
'status': 'request was permitted'
|
||||||
|
@ -72,22 +72,22 @@ The throttle classes provided by REST framework use Django's cache backend. You
|
||||||
|
|
||||||
## AnonRateThrottle
|
## AnonRateThrottle
|
||||||
|
|
||||||
The `AnonThrottle` will only ever throttle unauthenticated users. The IP address of the incoming request is used to generate a unique key to throttle against.
|
The `AnonRateThrottle` will only ever throttle unauthenticated users. The IP address of the incoming request is used to generate a unique key to throttle against.
|
||||||
|
|
||||||
The allowed request rate is determined from one of the following (in order of preference).
|
The allowed request rate is determined from one of the following (in order of preference).
|
||||||
|
|
||||||
* The `rate` property on the class, which may be provided by overriding `AnonThrottle` and setting the property.
|
* The `rate` property on the class, which may be provided by overriding `AnonRateThrottle` and setting the property.
|
||||||
* The `DEFAULT_THROTTLE_RATES['anon']` setting.
|
* The `DEFAULT_THROTTLE_RATES['anon']` setting.
|
||||||
|
|
||||||
`AnonThrottle` is suitable if you want to restrict the rate of requests from unknown sources.
|
`AnonRateThrottle` is suitable if you want to restrict the rate of requests from unknown sources.
|
||||||
|
|
||||||
## UserRateThrottle
|
## UserRateThrottle
|
||||||
|
|
||||||
The `UserThrottle` will throttle users to a given rate of requests across the API. The user id is used to generate a unique key to throttle against. Unauthenticated requests will fall back to using the IP address of the incoming request to generate a unique key to throttle against.
|
The `UserRateThrottle` will throttle users to a given rate of requests across the API. The user id is used to generate a unique key to throttle against. Unauthenticated requests will fall back to using the IP address of the incoming request to generate a unique key to throttle against.
|
||||||
|
|
||||||
The allowed request rate is determined from one of the following (in order of preference).
|
The allowed request rate is determined from one of the following (in order of preference).
|
||||||
|
|
||||||
* The `rate` property on the class, which may be provided by overriding `UserThrottle` and setting the property.
|
* The `rate` property on the class, which may be provided by overriding `UserRateThrottle` and setting the property.
|
||||||
* The `DEFAULT_THROTTLE_RATES['user']` setting.
|
* The `DEFAULT_THROTTLE_RATES['user']` setting.
|
||||||
|
|
||||||
An API may have multiple `UserRateThrottles` in place at the same time. To do so, override `UserRateThrottle` and set a unique "scope" for each class.
|
An API may have multiple `UserRateThrottles` in place at the same time. To do so, override `UserRateThrottle` and set a unique "scope" for each class.
|
||||||
|
@ -113,11 +113,11 @@ For example, multiple user throttle rates could be implemented by using the foll
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
`UserThrottle` is suitable if you want simple global rate restrictions per-user.
|
`UserRateThrottle` is suitable if you want simple global rate restrictions per-user.
|
||||||
|
|
||||||
## ScopedRateThrottle
|
## ScopedRateThrottle
|
||||||
|
|
||||||
The `ScopedThrottle` class can be used to restrict access to specific parts of the API. This throttle will only be applied if the view that is being accessed includes a `.throttle_scope` property. The unique throttle key will then be formed by concatenating the "scope" of the request with the unique user id or IP address.
|
The `ScopedRateThrottle` class can be used to restrict access to specific parts of the API. This throttle will only be applied if the view that is being accessed includes a `.throttle_scope` property. The unique throttle key will then be formed by concatenating the "scope" of the request with the unique user id or IP address.
|
||||||
|
|
||||||
The allowed request rate is determined by the `DEFAULT_THROTTLE_RATES` setting using a key from the request "scope".
|
The allowed request rate is determined by the `DEFAULT_THROTTLE_RATES` setting using a key from the request "scope".
|
||||||
|
|
||||||
|
|
|
@ -137,11 +137,11 @@ The core of this functionality is the `api_view` decorator, which takes a list o
|
||||||
return Response({"message": "Hello, world!"})
|
return Response({"message": "Hello, world!"})
|
||||||
|
|
||||||
|
|
||||||
This view will use the default renderers, parsers, authentication classes etc specified in the [settings](settings).
|
This view will use the default renderers, parsers, authentication classes etc specified in the [settings].
|
||||||
|
|
||||||
## API policy decorators
|
## API policy decorators
|
||||||
|
|
||||||
To override the default settings, REST framework provides a set of additional decorators which can be added to your views. These must come *after* (below) the `@api_view` decorator. For example, to create a view that uses a [throttle](throttling) to ensure it can only be called once per day by a particular user, use the `@throttle_classes` decorator, passing a list of throttle classes:
|
To override the default settings, REST framework provides a set of additional decorators which can be added to your views. These must come *after* (below) the `@api_view` decorator. For example, to create a view that uses a [throttle][throttling] to ensure it can only be called once per day by a particular user, use the `@throttle_classes` decorator, passing a list of throttle classes:
|
||||||
|
|
||||||
from rest_framework.decorators import api_view, throttle_classes
|
from rest_framework.decorators import api_view, throttle_classes
|
||||||
from rest_framework.throttling import UserRateThrottle
|
from rest_framework.throttling import UserRateThrottle
|
||||||
|
|
|
@ -98,8 +98,10 @@ For example:
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from rest_framework import viewsets
|
from rest_framework import viewsets
|
||||||
|
from rest_framework import status
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from myapp.serializers import UserSerializer
|
from rest_framework.response import Response
|
||||||
|
from myapp.serializers import UserSerializer, PasswordSerializer
|
||||||
|
|
||||||
class UserViewSet(viewsets.ModelViewSet):
|
class UserViewSet(viewsets.ModelViewSet):
|
||||||
"""
|
"""
|
||||||
|
@ -176,7 +178,7 @@ Note that you can use any of the standard attributes or method overrides provide
|
||||||
permission_classes = [IsAccountAdminOrReadOnly]
|
permission_classes = [IsAccountAdminOrReadOnly]
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return request.user.accounts.all()
|
return self.request.user.accounts.all()
|
||||||
|
|
||||||
Also note that although this class provides the complete set of create/list/retrieve/update/destroy actions by default, you can restrict the available operations by using the standard permission classes.
|
Also note that although this class provides the complete set of create/list/retrieve/update/destroy actions by default, you can restrict the available operations by using the standard permission classes.
|
||||||
|
|
||||||
|
@ -205,9 +207,9 @@ You may need to provide custom `ViewSet` classes that do not have the full set o
|
||||||
|
|
||||||
To create a base viewset class that provides `create`, `list` and `retrieve` operations, inherit from `GenericViewSet`, and mixin the required actions:
|
To create a base viewset class that provides `create`, `list` and `retrieve` operations, inherit from `GenericViewSet`, and mixin the required actions:
|
||||||
|
|
||||||
class CreateListRetrieveViewSet(mixins.CreateMixin,
|
class CreateListRetrieveViewSet(mixins.CreateModelMixin,
|
||||||
mixins.ListMixin,
|
mixins.ListModelMixin,
|
||||||
mixins.RetrieveMixin,
|
mixins.RetrieveModelMixin,
|
||||||
viewsets.GenericViewSet):
|
viewsets.GenericViewSet):
|
||||||
"""
|
"""
|
||||||
A viewset that provides `retrieve`, `update`, and `list` actions.
|
A viewset that provides `retrieve`, `update`, and `list` actions.
|
||||||
|
|
BIN
docs/img/autocomplete.png
Normal file
BIN
docs/img/autocomplete.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
|
@ -23,7 +23,7 @@ To guard against these type of attacks, you need to do two things:
|
||||||
|
|
||||||
If you're using `SessionAuthentication` you'll need to include valid CSRF tokens for any `POST`, `PUT`, `PATCH` or `DELETE` operations.
|
If you're using `SessionAuthentication` you'll need to include valid CSRF tokens for any `POST`, `PUT`, `PATCH` or `DELETE` operations.
|
||||||
|
|
||||||
The Django documentation describes how to [include CSRF tokens in AJAX requests][csrf-ajax].
|
In order to make AJAX requests, you need to include CSRF token in the HTTP header, as [described in the Django documentation][csrf-ajax].
|
||||||
|
|
||||||
## CORS
|
## CORS
|
||||||
|
|
||||||
|
|
|
@ -75,6 +75,7 @@ All of the blocks available in the browsable API base template that can be used
|
||||||
* `branding` - Branding section of the navbar, see [Bootstrap components][bcomponentsnav].
|
* `branding` - Branding section of the navbar, see [Bootstrap components][bcomponentsnav].
|
||||||
* `breadcrumbs` - Links showing resource nesting, allowing the user to go back up the resources. It's recommended to preserve these, but they can be overridden using the breadcrumbs block.
|
* `breadcrumbs` - Links showing resource nesting, allowing the user to go back up the resources. It's recommended to preserve these, but they can be overridden using the breadcrumbs block.
|
||||||
* `footer` - Any copyright notices or similar footer materials can go here (by default right-aligned).
|
* `footer` - Any copyright notices or similar footer materials can go here (by default right-aligned).
|
||||||
|
* `script` - JavaScript files for the page.
|
||||||
* `style` - CSS stylesheets for the page.
|
* `style` - CSS stylesheets for the page.
|
||||||
* `title` - Title of the page.
|
* `title` - Title of the page.
|
||||||
* `userlinks` - This is a list of links on the right of the header, by default containing login/logout links. To add links instead of replace, use `{{ block.super }}` to preserve the authentication links.
|
* `userlinks` - This is a list of links on the right of the header, by default containing login/logout links. To add links instead of replace, use `{{ block.super }}` to preserve the authentication links.
|
||||||
|
@ -89,7 +90,7 @@ The browsable API makes use of the Bootstrap tooltips component. Any element wi
|
||||||
|
|
||||||
### Login Template
|
### Login Template
|
||||||
|
|
||||||
To add branding and customize the look-and-feel of the login template, create a template called `login.html` and add it to your project, eg: `templates/rest_framework/login.html`. The template should extend from `rest_framework/base_login.html`.
|
To add branding and customize the look-and-feel of the login template, create a template called `login.html` and add it to your project, eg: `templates/rest_framework/login.html`. The template should extend from `rest_framework/login_base.html`.
|
||||||
|
|
||||||
You can add your site name or branding by including the branding block:
|
You can add your site name or branding by including the branding block:
|
||||||
|
|
||||||
|
@ -125,6 +126,37 @@ The context that's available to the template:
|
||||||
|
|
||||||
For more advanced customization, such as not having a Bootstrap basis or tighter integration with the rest of your site, you can simply choose not to have `api.html` extend `base.html`. Then the page content and capabilities are entirely up to you.
|
For more advanced customization, such as not having a Bootstrap basis or tighter integration with the rest of your site, you can simply choose not to have `api.html` extend `base.html`. Then the page content and capabilities are entirely up to you.
|
||||||
|
|
||||||
|
#### Autocompletion
|
||||||
|
|
||||||
|
When a `ChoiceField` has too many items, rendering the widget containing all the options can become very slow, and cause the browsable API rendering to perform poorly. One solution is to replace the selector by an autocomplete widget, that only loads and renders a subset of the available options as needed.
|
||||||
|
|
||||||
|
There are [a variety of packages for autocomplete widgets][autocomplete-packages], such as [django-autocomplete-light][django-autocomplete-light]. To setup `django-autocomplete-light`, follow the [installation documentation][django-autocomplete-light-install], add the the following to the `api.html` template:
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
{{ block.super }}
|
||||||
|
{% include 'autocomplete_light/static.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
You can now add the `autocomplete_light.ChoiceWidget` widget to the serializer field.
|
||||||
|
|
||||||
|
import autocomplete_light
|
||||||
|
|
||||||
|
class BookSerializer(serializers.ModelSerializer):
|
||||||
|
author = serializers.ChoiceField(
|
||||||
|
widget=autocomplete_light.ChoiceWidget('AuthorAutocomplete')
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Book
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
![Autocomplete][autocomplete-image]
|
||||||
|
|
||||||
|
*Screenshot of the autocomplete-light widget*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
[cite]: http://en.wikiquote.org/wiki/Alfred_North_Whitehead
|
[cite]: http://en.wikiquote.org/wiki/Alfred_North_Whitehead
|
||||||
[drfreverse]: ../api-guide/reverse.md
|
[drfreverse]: ../api-guide/reverse.md
|
||||||
[ffjsonview]: https://addons.mozilla.org/en-US/firefox/addon/jsonview/
|
[ffjsonview]: https://addons.mozilla.org/en-US/firefox/addon/jsonview/
|
||||||
|
@ -136,4 +168,7 @@ For more advanced customization, such as not having a Bootstrap basis or tighter
|
||||||
[bswatch]: http://bootswatch.com/
|
[bswatch]: http://bootswatch.com/
|
||||||
[bcomponents]: http://twitter.github.com/bootstrap/components.html
|
[bcomponents]: http://twitter.github.com/bootstrap/components.html
|
||||||
[bcomponentsnav]: http://twitter.github.com/bootstrap/components.html#navbar
|
[bcomponentsnav]: http://twitter.github.com/bootstrap/components.html#navbar
|
||||||
|
[autocomplete-packages]: https://www.djangopackages.com/grids/g/auto-complete/
|
||||||
|
[django-autocomplete-light]: https://github.com/yourlabs/django-autocomplete-light
|
||||||
|
[django-autocomplete-light-install]: http://django-autocomplete-light.readthedocs.org/en/latest/#install
|
||||||
|
[autocomplete-image]: ../img/autocomplete.png
|
||||||
|
|
|
@ -145,6 +145,13 @@ The following people have helped make REST framework great.
|
||||||
* Philip Douglas - [freakydug]
|
* Philip Douglas - [freakydug]
|
||||||
* Igor Kalat - [trwired]
|
* Igor Kalat - [trwired]
|
||||||
* Rudolf Olah - [omouse]
|
* Rudolf Olah - [omouse]
|
||||||
|
* Gertjan Oude Lohuis - [gertjanol]
|
||||||
|
* Matthias Jacob - [cyroxx]
|
||||||
|
* Pavel Zinovkin - [pzinovkin]
|
||||||
|
* Will Kahn-Greene - [willkg]
|
||||||
|
* Kevin Brown - [kevin-brown]
|
||||||
|
* Rodrigo Martell - [coderigo]
|
||||||
|
* James Rutherford - [jimr]
|
||||||
|
|
||||||
Many thanks to everyone who's contributed to the project.
|
Many thanks to everyone who's contributed to the project.
|
||||||
|
|
||||||
|
@ -326,3 +333,10 @@ You can also contact [@_tomchristie][twitter] directly on twitter.
|
||||||
[freakydug]: https://github.com/freakydug
|
[freakydug]: https://github.com/freakydug
|
||||||
[trwired]: https://github.com/trwired
|
[trwired]: https://github.com/trwired
|
||||||
[omouse]: https://github.com/omouse
|
[omouse]: https://github.com/omouse
|
||||||
|
[gertjanol]: https://github.com/gertjanol
|
||||||
|
[cyroxx]: https://github.com/cyroxx
|
||||||
|
[pzinovkin]: https://github.com/pzinovkin
|
||||||
|
[coderigo]: https://github.com/coderigo
|
||||||
|
[willkg]: https://github.com/willkg
|
||||||
|
[kevin-brown]: https://github.com/kevin-brown
|
||||||
|
[jimr]: https://github.com/jimr
|
||||||
|
|
|
@ -80,7 +80,7 @@ We can easily re-write our existing serializers to use hyperlinking.
|
||||||
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
|
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.Snippet
|
model = Snippet
|
||||||
fields = ('url', 'highlight', 'owner',
|
fields = ('url', 'highlight', 'owner',
|
||||||
'title', 'code', 'linenos', 'language', 'style')
|
'title', 'code', 'linenos', 'language', 'style')
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,12 @@ def get_authorization_header(request):
|
||||||
return auth
|
return auth
|
||||||
|
|
||||||
|
|
||||||
|
class CSRFCheck(CsrfViewMiddleware):
|
||||||
|
def _reject(self, request, reason):
|
||||||
|
# Return the failure reason instead of an HttpResponse
|
||||||
|
return reason
|
||||||
|
|
||||||
|
|
||||||
class BaseAuthentication(object):
|
class BaseAuthentication(object):
|
||||||
"""
|
"""
|
||||||
All authentication classes should extend BaseAuthentication.
|
All authentication classes should extend BaseAuthentication.
|
||||||
|
@ -103,27 +109,27 @@ class SessionAuthentication(BaseAuthentication):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Get the underlying HttpRequest object
|
# Get the underlying HttpRequest object
|
||||||
http_request = request._request
|
request = request._request
|
||||||
user = getattr(http_request, 'user', None)
|
user = getattr(request, 'user', None)
|
||||||
|
|
||||||
# Unauthenticated, CSRF validation not required
|
# Unauthenticated, CSRF validation not required
|
||||||
if not user or not user.is_active:
|
if not user or not user.is_active:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Enforce CSRF validation for session based authentication.
|
self.enforce_csrf(request)
|
||||||
class CSRFCheck(CsrfViewMiddleware):
|
|
||||||
def _reject(self, request, reason):
|
|
||||||
# Return the failure reason instead of an HttpResponse
|
|
||||||
return reason
|
|
||||||
|
|
||||||
reason = CSRFCheck().process_view(http_request, None, (), {})
|
|
||||||
if reason:
|
|
||||||
# CSRF failed, bail with explicit error message
|
|
||||||
raise exceptions.AuthenticationFailed('CSRF Failed: %s' % reason)
|
|
||||||
|
|
||||||
# CSRF passed with authenticated user
|
# CSRF passed with authenticated user
|
||||||
return (user, None)
|
return (user, None)
|
||||||
|
|
||||||
|
def enforce_csrf(self, request):
|
||||||
|
"""
|
||||||
|
Enforce CSRF validation for session based authentication.
|
||||||
|
"""
|
||||||
|
reason = CSRFCheck().process_view(request, None, (), {})
|
||||||
|
if reason:
|
||||||
|
# CSRF failed, bail with explicit error message
|
||||||
|
raise exceptions.AuthenticationFailed('CSRF Failed: %s' % reason)
|
||||||
|
|
||||||
|
|
||||||
class TokenAuthentication(BaseAuthentication):
|
class TokenAuthentication(BaseAuthentication):
|
||||||
"""
|
"""
|
||||||
|
|
11
rest_framework/authtoken/admin.py
Normal file
11
rest_framework/authtoken/admin.py
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
from django.contrib import admin
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
class TokenAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('key', 'user', 'created')
|
||||||
|
fields = ('user',)
|
||||||
|
ordering = ('-created',)
|
||||||
|
|
||||||
|
|
||||||
|
admin.site.register(Token, TokenAdmin)
|
|
@ -8,6 +8,7 @@ from __future__ import unicode_literals
|
||||||
|
|
||||||
import django
|
import django
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
# Try to import six from Django, fallback to included `six`.
|
# Try to import six from Django, fallback to included `six`.
|
||||||
try:
|
try:
|
||||||
|
@ -83,7 +84,6 @@ def get_concrete_model(model_cls):
|
||||||
|
|
||||||
# Django 1.5 add support for custom auth user model
|
# Django 1.5 add support for custom auth user model
|
||||||
if django.VERSION >= (1, 5):
|
if django.VERSION >= (1, 5):
|
||||||
from django.conf import settings
|
|
||||||
AUTH_USER_MODEL = settings.AUTH_USER_MODEL
|
AUTH_USER_MODEL = settings.AUTH_USER_MODEL
|
||||||
else:
|
else:
|
||||||
AUTH_USER_MODEL = 'auth.User'
|
AUTH_USER_MODEL = 'auth.User'
|
||||||
|
@ -436,6 +436,42 @@ except ImportError:
|
||||||
return force_text(url)
|
return force_text(url)
|
||||||
|
|
||||||
|
|
||||||
|
# RequestFactory only provide `generic` from 1.5 onwards
|
||||||
|
|
||||||
|
from django.test.client import RequestFactory as DjangoRequestFactory
|
||||||
|
from django.test.client import FakePayload
|
||||||
|
try:
|
||||||
|
# In 1.5 the test client uses force_bytes
|
||||||
|
from django.utils.encoding import force_bytes_or_smart_bytes
|
||||||
|
except ImportError:
|
||||||
|
# In 1.3 and 1.4 the test client just uses smart_str
|
||||||
|
from django.utils.encoding import smart_str as force_bytes_or_smart_bytes
|
||||||
|
|
||||||
|
|
||||||
|
class RequestFactory(DjangoRequestFactory):
|
||||||
|
def generic(self, method, path,
|
||||||
|
data='', content_type='application/octet-stream', **extra):
|
||||||
|
parsed = urlparse.urlparse(path)
|
||||||
|
data = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET)
|
||||||
|
r = {
|
||||||
|
'PATH_INFO': self._get_path(parsed),
|
||||||
|
'QUERY_STRING': force_text(parsed[4]),
|
||||||
|
'REQUEST_METHOD': str(method),
|
||||||
|
}
|
||||||
|
if data:
|
||||||
|
r.update({
|
||||||
|
'CONTENT_LENGTH': len(data),
|
||||||
|
'CONTENT_TYPE': str(content_type),
|
||||||
|
'wsgi.input': FakePayload(data),
|
||||||
|
})
|
||||||
|
elif django.VERSION <= (1, 4):
|
||||||
|
# For 1.3 we need an empty WSGI payload
|
||||||
|
r.update({
|
||||||
|
'wsgi.input': FakePayload('')
|
||||||
|
})
|
||||||
|
r.update(extra)
|
||||||
|
return self.request(**r)
|
||||||
|
|
||||||
# Markdown is optional
|
# Markdown is optional
|
||||||
try:
|
try:
|
||||||
import markdown
|
import markdown
|
||||||
|
|
|
@ -100,6 +100,19 @@ def humanize_strptime(format_string):
|
||||||
return format_string
|
return format_string
|
||||||
|
|
||||||
|
|
||||||
|
def strip_multiple_choice_msg(help_text):
|
||||||
|
"""
|
||||||
|
Remove the 'Hold down "control" ...' message that is Django enforces in
|
||||||
|
select multiple fields on ModelForms. (Required for 1.5 and earlier)
|
||||||
|
|
||||||
|
See https://code.djangoproject.com/ticket/9321
|
||||||
|
"""
|
||||||
|
multiple_choice_msg = _(' Hold down "Control", or "Command" on a Mac, to select more than one.')
|
||||||
|
multiple_choice_msg = force_text(multiple_choice_msg)
|
||||||
|
|
||||||
|
return help_text.replace(multiple_choice_msg, '')
|
||||||
|
|
||||||
|
|
||||||
class Field(object):
|
class Field(object):
|
||||||
read_only = True
|
read_only = True
|
||||||
creation_counter = 0
|
creation_counter = 0
|
||||||
|
@ -122,7 +135,7 @@ class Field(object):
|
||||||
self.label = smart_text(label)
|
self.label = smart_text(label)
|
||||||
|
|
||||||
if help_text is not None:
|
if help_text is not None:
|
||||||
self.help_text = smart_text(help_text)
|
self.help_text = strip_multiple_choice_msg(smart_text(help_text))
|
||||||
|
|
||||||
def initialize(self, parent, field_name):
|
def initialize(self, parent, field_name):
|
||||||
"""
|
"""
|
||||||
|
@ -499,7 +512,7 @@ class EmailField(CharField):
|
||||||
form_field_class = forms.EmailField
|
form_field_class = forms.EmailField
|
||||||
|
|
||||||
default_error_messages = {
|
default_error_messages = {
|
||||||
'invalid': _('Enter a valid e-mail address.'),
|
'invalid': _('Enter a valid email address.'),
|
||||||
}
|
}
|
||||||
default_validators = [validators.validate_email]
|
default_validators = [validators.validate_email]
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ from django import forms
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.http.multipartparser import parse_header
|
from django.http.multipartparser import parse_header
|
||||||
from django.template import RequestContext, loader, Template
|
from django.template import RequestContext, loader, Template
|
||||||
|
from django.test.client import encode_multipart
|
||||||
from django.utils.xmlutils import SimplerXMLGenerator
|
from django.utils.xmlutils import SimplerXMLGenerator
|
||||||
from rest_framework.compat import StringIO
|
from rest_framework.compat import StringIO
|
||||||
from rest_framework.compat import six
|
from rest_framework.compat import six
|
||||||
|
@ -571,3 +572,13 @@ class BrowsableAPIRenderer(BaseRenderer):
|
||||||
response.status_code = status.HTTP_200_OK
|
response.status_code = status.HTTP_200_OK
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class MultiPartRenderer(BaseRenderer):
|
||||||
|
media_type = 'multipart/form-data; boundary=BoUnDaRyStRiNg'
|
||||||
|
format = 'multipart'
|
||||||
|
charset = 'utf-8'
|
||||||
|
BOUNDARY = 'BoUnDaRyStRiNg'
|
||||||
|
|
||||||
|
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||||
|
return encode_multipart(self.BOUNDARY, data)
|
||||||
|
|
|
@ -64,6 +64,20 @@ def clone_request(request, method):
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
class ForcedAuthentication(object):
|
||||||
|
"""
|
||||||
|
This authentication class is used if the test client or request factory
|
||||||
|
forcibly authenticated the request.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, force_user, force_token):
|
||||||
|
self.force_user = force_user
|
||||||
|
self.force_token = force_token
|
||||||
|
|
||||||
|
def authenticate(self, request):
|
||||||
|
return (self.force_user, self.force_token)
|
||||||
|
|
||||||
|
|
||||||
class Request(object):
|
class Request(object):
|
||||||
"""
|
"""
|
||||||
Wrapper allowing to enhance a standard `HttpRequest` instance.
|
Wrapper allowing to enhance a standard `HttpRequest` instance.
|
||||||
|
@ -98,6 +112,12 @@ class Request(object):
|
||||||
self.parser_context['request'] = self
|
self.parser_context['request'] = self
|
||||||
self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
|
self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET
|
||||||
|
|
||||||
|
force_user = getattr(request, '_force_auth_user', None)
|
||||||
|
force_token = getattr(request, '_force_auth_token', None)
|
||||||
|
if (force_user is not None or force_token is not None):
|
||||||
|
forced_auth = ForcedAuthentication(force_user, force_token)
|
||||||
|
self.authenticators = (forced_auth,)
|
||||||
|
|
||||||
def _default_negotiator(self):
|
def _default_negotiator(self):
|
||||||
return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()
|
return api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS()
|
||||||
|
|
||||||
|
|
|
@ -690,7 +690,7 @@ class ModelSerializer(Serializer):
|
||||||
assert field_name in ret, \
|
assert field_name in ret, \
|
||||||
"Noexistant field '%s' specified in `read_only_fields` " \
|
"Noexistant field '%s' specified in `read_only_fields` " \
|
||||||
"on serializer '%s'." % \
|
"on serializer '%s'." % \
|
||||||
(self.__class__.__name__, field_name)
|
(field_name, self.__class__.__name__)
|
||||||
ret[field_name].read_only = True
|
ret[field_name].read_only = True
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -73,6 +73,13 @@ DEFAULTS = {
|
||||||
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
|
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
|
||||||
'UNAUTHENTICATED_TOKEN': None,
|
'UNAUTHENTICATED_TOKEN': None,
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
'TEST_REQUEST_RENDERER_CLASSES': (
|
||||||
|
'rest_framework.renderers.MultiPartRenderer',
|
||||||
|
'rest_framework.renderers.JSONRenderer'
|
||||||
|
),
|
||||||
|
'TEST_REQUEST_DEFAULT_FORMAT': 'multipart',
|
||||||
|
|
||||||
# Browser enhancements
|
# Browser enhancements
|
||||||
'FORM_METHOD_OVERRIDE': '_method',
|
'FORM_METHOD_OVERRIDE': '_method',
|
||||||
'FORM_CONTENT_OVERRIDE': '_content',
|
'FORM_CONTENT_OVERRIDE': '_content',
|
||||||
|
@ -115,6 +122,7 @@ IMPORT_STRINGS = (
|
||||||
'DEFAULT_PAGINATION_SERIALIZER_CLASS',
|
'DEFAULT_PAGINATION_SERIALIZER_CLASS',
|
||||||
'DEFAULT_FILTER_BACKENDS',
|
'DEFAULT_FILTER_BACKENDS',
|
||||||
'FILTER_BACKEND',
|
'FILTER_BACKEND',
|
||||||
|
'TEST_REQUEST_RENDERER_CLASSES',
|
||||||
'UNAUTHENTICATED_USER',
|
'UNAUTHENTICATED_USER',
|
||||||
'UNAUTHENTICATED_TOKEN',
|
'UNAUTHENTICATED_TOKEN',
|
||||||
)
|
)
|
||||||
|
|
157
rest_framework/test.py
Normal file
157
rest_framework/test.py
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
# -- coding: utf-8 --
|
||||||
|
|
||||||
|
# Note that we import as `DjangoRequestFactory` and `DjangoClient` in order
|
||||||
|
# to make it harder for the user to import the wrong thing without realizing.
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
import django
|
||||||
|
from django.conf import settings
|
||||||
|
from django.test.client import Client as DjangoClient
|
||||||
|
from django.test.client import ClientHandler
|
||||||
|
from django.test import testcases
|
||||||
|
from rest_framework.settings import api_settings
|
||||||
|
from rest_framework.compat import RequestFactory as DjangoRequestFactory
|
||||||
|
from rest_framework.compat import force_bytes_or_smart_bytes, six
|
||||||
|
|
||||||
|
|
||||||
|
def force_authenticate(request, user=None, token=None):
|
||||||
|
request._force_auth_user = user
|
||||||
|
request._force_auth_token = token
|
||||||
|
|
||||||
|
|
||||||
|
class APIRequestFactory(DjangoRequestFactory):
|
||||||
|
renderer_classes_list = api_settings.TEST_REQUEST_RENDERER_CLASSES
|
||||||
|
default_format = api_settings.TEST_REQUEST_DEFAULT_FORMAT
|
||||||
|
|
||||||
|
def __init__(self, enforce_csrf_checks=False, **defaults):
|
||||||
|
self.enforce_csrf_checks = enforce_csrf_checks
|
||||||
|
self.renderer_classes = {}
|
||||||
|
for cls in self.renderer_classes_list:
|
||||||
|
self.renderer_classes[cls.format] = cls
|
||||||
|
super(APIRequestFactory, self).__init__(**defaults)
|
||||||
|
|
||||||
|
def _encode_data(self, data, format=None, content_type=None):
|
||||||
|
"""
|
||||||
|
Encode the data returning a two tuple of (bytes, content_type)
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return ('', None)
|
||||||
|
|
||||||
|
assert format is None or content_type is None, (
|
||||||
|
'You may not set both `format` and `content_type`.'
|
||||||
|
)
|
||||||
|
|
||||||
|
if content_type:
|
||||||
|
# Content type specified explicitly, treat data as a raw bytestring
|
||||||
|
ret = force_bytes_or_smart_bytes(data, settings.DEFAULT_CHARSET)
|
||||||
|
|
||||||
|
else:
|
||||||
|
format = format or self.default_format
|
||||||
|
|
||||||
|
assert format in self.renderer_classes, ("Invalid format '{0}'. "
|
||||||
|
"Available formats are {1}. Set TEST_REQUEST_RENDERER_CLASSES "
|
||||||
|
"to enable extra request formats.".format(
|
||||||
|
format,
|
||||||
|
', '.join(["'" + fmt + "'" for fmt in self.renderer_classes.keys()])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use format and render the data into a bytestring
|
||||||
|
renderer = self.renderer_classes[format]()
|
||||||
|
ret = renderer.render(data)
|
||||||
|
|
||||||
|
# Determine the content-type header from the renderer
|
||||||
|
content_type = "{0}; charset={1}".format(
|
||||||
|
renderer.media_type, renderer.charset
|
||||||
|
)
|
||||||
|
|
||||||
|
# Coerce text to bytes if required.
|
||||||
|
if isinstance(ret, six.text_type):
|
||||||
|
ret = bytes(ret.encode(renderer.charset))
|
||||||
|
|
||||||
|
return ret, content_type
|
||||||
|
|
||||||
|
def post(self, path, data=None, format=None, content_type=None, **extra):
|
||||||
|
data, content_type = self._encode_data(data, format, content_type)
|
||||||
|
return self.generic('POST', path, data, content_type, **extra)
|
||||||
|
|
||||||
|
def put(self, path, data=None, format=None, content_type=None, **extra):
|
||||||
|
data, content_type = self._encode_data(data, format, content_type)
|
||||||
|
return self.generic('PUT', path, data, content_type, **extra)
|
||||||
|
|
||||||
|
def patch(self, path, data=None, format=None, content_type=None, **extra):
|
||||||
|
data, content_type = self._encode_data(data, format, content_type)
|
||||||
|
return self.generic('PATCH', path, data, content_type, **extra)
|
||||||
|
|
||||||
|
def delete(self, path, data=None, format=None, content_type=None, **extra):
|
||||||
|
data, content_type = self._encode_data(data, format, content_type)
|
||||||
|
return self.generic('DELETE', path, data, content_type, **extra)
|
||||||
|
|
||||||
|
def options(self, path, data=None, format=None, content_type=None, **extra):
|
||||||
|
data, content_type = self._encode_data(data, format, content_type)
|
||||||
|
return self.generic('OPTIONS', path, data, content_type, **extra)
|
||||||
|
|
||||||
|
def request(self, **kwargs):
|
||||||
|
request = super(APIRequestFactory, self).request(**kwargs)
|
||||||
|
request._dont_enforce_csrf_checks = not self.enforce_csrf_checks
|
||||||
|
return request
|
||||||
|
|
||||||
|
|
||||||
|
class ForceAuthClientHandler(ClientHandler):
|
||||||
|
"""
|
||||||
|
A patched version of ClientHandler that can enforce authentication
|
||||||
|
on the outgoing requests.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self._force_user = None
|
||||||
|
self._force_token = None
|
||||||
|
super(ForceAuthClientHandler, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_response(self, request):
|
||||||
|
# This is the simplest place we can hook into to patch the
|
||||||
|
# request object.
|
||||||
|
force_authenticate(request, self._force_user, self._force_token)
|
||||||
|
return super(ForceAuthClientHandler, self).get_response(request)
|
||||||
|
|
||||||
|
|
||||||
|
class APIClient(APIRequestFactory, DjangoClient):
|
||||||
|
def __init__(self, enforce_csrf_checks=False, **defaults):
|
||||||
|
super(APIClient, self).__init__(**defaults)
|
||||||
|
self.handler = ForceAuthClientHandler(enforce_csrf_checks)
|
||||||
|
self._credentials = {}
|
||||||
|
|
||||||
|
def credentials(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Sets headers that will be used on every outgoing request.
|
||||||
|
"""
|
||||||
|
self._credentials = kwargs
|
||||||
|
|
||||||
|
def force_authenticate(self, user=None, token=None):
|
||||||
|
"""
|
||||||
|
Forcibly authenticates outgoing requests with the given
|
||||||
|
user and/or token.
|
||||||
|
"""
|
||||||
|
self.handler._force_user = user
|
||||||
|
self.handler._force_token = token
|
||||||
|
|
||||||
|
def request(self, **kwargs):
|
||||||
|
# Ensure that any credentials set get added to every request.
|
||||||
|
kwargs.update(self._credentials)
|
||||||
|
return super(APIClient, self).request(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class APITransactionTestCase(testcases.TransactionTestCase):
|
||||||
|
client_class = APIClient
|
||||||
|
|
||||||
|
|
||||||
|
class APITestCase(testcases.TestCase):
|
||||||
|
client_class = APIClient
|
||||||
|
|
||||||
|
|
||||||
|
if django.VERSION >= (1, 4):
|
||||||
|
class APISimpleTestCase(testcases.SimpleTestCase):
|
||||||
|
client_class = APIClient
|
||||||
|
|
||||||
|
class APILiveServerTestCase(testcases.LiveServerTestCase):
|
||||||
|
client_class = APIClient
|
|
@ -52,7 +52,7 @@ class CallableDefaultValueModel(RESTFrameworkModel):
|
||||||
|
|
||||||
|
|
||||||
class ManyToManyModel(RESTFrameworkModel):
|
class ManyToManyModel(RESTFrameworkModel):
|
||||||
rel = models.ManyToManyField(Anchor)
|
rel = models.ManyToManyField(Anchor, help_text='Some help text.')
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyManyToManyModel(RESTFrameworkModel):
|
class ReadOnlyManyToManyModel(RESTFrameworkModel):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.test import Client, TestCase
|
from django.test import TestCase
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
from rest_framework import HTTP_HEADER_ENCODING
|
from rest_framework import HTTP_HEADER_ENCODING
|
||||||
from rest_framework import exceptions
|
from rest_framework import exceptions
|
||||||
|
@ -21,14 +21,13 @@ from rest_framework.authtoken.models import Token
|
||||||
from rest_framework.compat import patterns, url, include
|
from rest_framework.compat import patterns, url, include
|
||||||
from rest_framework.compat import oauth2_provider, oauth2_provider_models, oauth2_provider_scope
|
from rest_framework.compat import oauth2_provider, oauth2_provider_models, oauth2_provider_scope
|
||||||
from rest_framework.compat import oauth, oauth_provider
|
from rest_framework.compat import oauth, oauth_provider
|
||||||
from rest_framework.tests.utils import RequestFactory
|
from rest_framework.test import APIRequestFactory, APIClient
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
import json
|
|
||||||
import base64
|
import base64
|
||||||
import time
|
import time
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
class MockView(APIView):
|
class MockView(APIView):
|
||||||
|
@ -68,7 +67,7 @@ class BasicAuthTests(TestCase):
|
||||||
urls = 'rest_framework.tests.test_authentication'
|
urls = 'rest_framework.tests.test_authentication'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
self.csrf_client = APIClient(enforce_csrf_checks=True)
|
||||||
self.username = 'john'
|
self.username = 'john'
|
||||||
self.email = 'lennon@thebeatles.com'
|
self.email = 'lennon@thebeatles.com'
|
||||||
self.password = 'password'
|
self.password = 'password'
|
||||||
|
@ -87,7 +86,7 @@ class BasicAuthTests(TestCase):
|
||||||
credentials = ('%s:%s' % (self.username, self.password))
|
credentials = ('%s:%s' % (self.username, self.password))
|
||||||
base64_credentials = base64.b64encode(credentials.encode(HTTP_HEADER_ENCODING)).decode(HTTP_HEADER_ENCODING)
|
base64_credentials = base64.b64encode(credentials.encode(HTTP_HEADER_ENCODING)).decode(HTTP_HEADER_ENCODING)
|
||||||
auth = 'Basic %s' % base64_credentials
|
auth = 'Basic %s' % base64_credentials
|
||||||
response = self.csrf_client.post('/basic/', json.dumps({'example': 'example'}), 'application/json', HTTP_AUTHORIZATION=auth)
|
response = self.csrf_client.post('/basic/', {'example': 'example'}, format='json', HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
def test_post_form_failing_basic_auth(self):
|
def test_post_form_failing_basic_auth(self):
|
||||||
|
@ -97,7 +96,7 @@ class BasicAuthTests(TestCase):
|
||||||
|
|
||||||
def test_post_json_failing_basic_auth(self):
|
def test_post_json_failing_basic_auth(self):
|
||||||
"""Ensure POSTing json over basic auth without correct credentials fails"""
|
"""Ensure POSTing json over basic auth without correct credentials fails"""
|
||||||
response = self.csrf_client.post('/basic/', json.dumps({'example': 'example'}), 'application/json')
|
response = self.csrf_client.post('/basic/', {'example': 'example'}, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||||
self.assertEqual(response['WWW-Authenticate'], 'Basic realm="api"')
|
self.assertEqual(response['WWW-Authenticate'], 'Basic realm="api"')
|
||||||
|
|
||||||
|
@ -107,8 +106,8 @@ class SessionAuthTests(TestCase):
|
||||||
urls = 'rest_framework.tests.test_authentication'
|
urls = 'rest_framework.tests.test_authentication'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
self.csrf_client = APIClient(enforce_csrf_checks=True)
|
||||||
self.non_csrf_client = Client(enforce_csrf_checks=False)
|
self.non_csrf_client = APIClient(enforce_csrf_checks=False)
|
||||||
self.username = 'john'
|
self.username = 'john'
|
||||||
self.email = 'lennon@thebeatles.com'
|
self.email = 'lennon@thebeatles.com'
|
||||||
self.password = 'password'
|
self.password = 'password'
|
||||||
|
@ -154,7 +153,7 @@ class TokenAuthTests(TestCase):
|
||||||
urls = 'rest_framework.tests.test_authentication'
|
urls = 'rest_framework.tests.test_authentication'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
self.csrf_client = APIClient(enforce_csrf_checks=True)
|
||||||
self.username = 'john'
|
self.username = 'john'
|
||||||
self.email = 'lennon@thebeatles.com'
|
self.email = 'lennon@thebeatles.com'
|
||||||
self.password = 'password'
|
self.password = 'password'
|
||||||
|
@ -172,7 +171,7 @@ class TokenAuthTests(TestCase):
|
||||||
def test_post_json_passing_token_auth(self):
|
def test_post_json_passing_token_auth(self):
|
||||||
"""Ensure POSTing form over token auth with correct credentials passes and does not require CSRF"""
|
"""Ensure POSTing form over token auth with correct credentials passes and does not require CSRF"""
|
||||||
auth = "Token " + self.key
|
auth = "Token " + self.key
|
||||||
response = self.csrf_client.post('/token/', json.dumps({'example': 'example'}), 'application/json', HTTP_AUTHORIZATION=auth)
|
response = self.csrf_client.post('/token/', {'example': 'example'}, format='json', HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
def test_post_form_failing_token_auth(self):
|
def test_post_form_failing_token_auth(self):
|
||||||
|
@ -182,7 +181,7 @@ class TokenAuthTests(TestCase):
|
||||||
|
|
||||||
def test_post_json_failing_token_auth(self):
|
def test_post_json_failing_token_auth(self):
|
||||||
"""Ensure POSTing json over token auth without correct credentials fails"""
|
"""Ensure POSTing json over token auth without correct credentials fails"""
|
||||||
response = self.csrf_client.post('/token/', json.dumps({'example': 'example'}), 'application/json')
|
response = self.csrf_client.post('/token/', {'example': 'example'}, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
|
||||||
|
|
||||||
def test_token_has_auto_assigned_key_if_none_provided(self):
|
def test_token_has_auto_assigned_key_if_none_provided(self):
|
||||||
|
@ -193,33 +192,33 @@ class TokenAuthTests(TestCase):
|
||||||
|
|
||||||
def test_token_login_json(self):
|
def test_token_login_json(self):
|
||||||
"""Ensure token login view using JSON POST works."""
|
"""Ensure token login view using JSON POST works."""
|
||||||
client = Client(enforce_csrf_checks=True)
|
client = APIClient(enforce_csrf_checks=True)
|
||||||
response = client.post('/auth-token/',
|
response = client.post('/auth-token/',
|
||||||
json.dumps({'username': self.username, 'password': self.password}), 'application/json')
|
{'username': self.username, 'password': self.password}, format='json')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(json.loads(response.content.decode('ascii'))['token'], self.key)
|
self.assertEqual(response.data['token'], self.key)
|
||||||
|
|
||||||
def test_token_login_json_bad_creds(self):
|
def test_token_login_json_bad_creds(self):
|
||||||
"""Ensure token login view using JSON POST fails if bad credentials are used."""
|
"""Ensure token login view using JSON POST fails if bad credentials are used."""
|
||||||
client = Client(enforce_csrf_checks=True)
|
client = APIClient(enforce_csrf_checks=True)
|
||||||
response = client.post('/auth-token/',
|
response = client.post('/auth-token/',
|
||||||
json.dumps({'username': self.username, 'password': "badpass"}), 'application/json')
|
{'username': self.username, 'password': "badpass"}, format='json')
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
def test_token_login_json_missing_fields(self):
|
def test_token_login_json_missing_fields(self):
|
||||||
"""Ensure token login view using JSON POST fails if missing fields."""
|
"""Ensure token login view using JSON POST fails if missing fields."""
|
||||||
client = Client(enforce_csrf_checks=True)
|
client = APIClient(enforce_csrf_checks=True)
|
||||||
response = client.post('/auth-token/',
|
response = client.post('/auth-token/',
|
||||||
json.dumps({'username': self.username}), 'application/json')
|
{'username': self.username}, format='json')
|
||||||
self.assertEqual(response.status_code, 400)
|
self.assertEqual(response.status_code, 400)
|
||||||
|
|
||||||
def test_token_login_form(self):
|
def test_token_login_form(self):
|
||||||
"""Ensure token login view using form POST works."""
|
"""Ensure token login view using form POST works."""
|
||||||
client = Client(enforce_csrf_checks=True)
|
client = APIClient(enforce_csrf_checks=True)
|
||||||
response = client.post('/auth-token/',
|
response = client.post('/auth-token/',
|
||||||
{'username': self.username, 'password': self.password})
|
{'username': self.username, 'password': self.password})
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(json.loads(response.content.decode('ascii'))['token'], self.key)
|
self.assertEqual(response.data['token'], self.key)
|
||||||
|
|
||||||
|
|
||||||
class IncorrectCredentialsTests(TestCase):
|
class IncorrectCredentialsTests(TestCase):
|
||||||
|
@ -256,7 +255,7 @@ class OAuthTests(TestCase):
|
||||||
|
|
||||||
self.consts = consts
|
self.consts = consts
|
||||||
|
|
||||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
self.csrf_client = APIClient(enforce_csrf_checks=True)
|
||||||
self.username = 'john'
|
self.username = 'john'
|
||||||
self.email = 'lennon@thebeatles.com'
|
self.email = 'lennon@thebeatles.com'
|
||||||
self.password = 'password'
|
self.password = 'password'
|
||||||
|
@ -470,12 +469,13 @@ class OAuthTests(TestCase):
|
||||||
response = self.csrf_client.post('/oauth/', HTTP_AUTHORIZATION=auth)
|
response = self.csrf_client.post('/oauth/', HTTP_AUTHORIZATION=auth)
|
||||||
self.assertEqual(response.status_code, 401)
|
self.assertEqual(response.status_code, 401)
|
||||||
|
|
||||||
|
|
||||||
class OAuth2Tests(TestCase):
|
class OAuth2Tests(TestCase):
|
||||||
"""OAuth 2.0 authentication"""
|
"""OAuth 2.0 authentication"""
|
||||||
urls = 'rest_framework.tests.test_authentication'
|
urls = 'rest_framework.tests.test_authentication'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
self.csrf_client = APIClient(enforce_csrf_checks=True)
|
||||||
self.username = 'john'
|
self.username = 'john'
|
||||||
self.email = 'lennon@thebeatles.com'
|
self.email = 'lennon@thebeatles.com'
|
||||||
self.password = 'password'
|
self.password = 'password'
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
from rest_framework.authentication import BasicAuthentication
|
||||||
|
from rest_framework.parsers import JSONParser
|
||||||
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.renderers import JSONRenderer
|
from rest_framework.renderers import JSONRenderer
|
||||||
from rest_framework.parsers import JSONParser
|
from rest_framework.test import APIRequestFactory
|
||||||
from rest_framework.authentication import BasicAuthentication
|
|
||||||
from rest_framework.throttling import UserRateThrottle
|
from rest_framework.throttling import UserRateThrottle
|
||||||
from rest_framework.permissions import IsAuthenticated
|
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.decorators import (
|
from rest_framework.decorators import (
|
||||||
api_view,
|
api_view,
|
||||||
|
@ -17,13 +18,11 @@ from rest_framework.decorators import (
|
||||||
permission_classes,
|
permission_classes,
|
||||||
)
|
)
|
||||||
|
|
||||||
from rest_framework.tests.utils import RequestFactory
|
|
||||||
|
|
||||||
|
|
||||||
class DecoratorTestCase(TestCase):
|
class DecoratorTestCase(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.factory = RequestFactory()
|
self.factory = APIRequestFactory()
|
||||||
|
|
||||||
def _finalize_response(self, request, response, *args, **kwargs):
|
def _finalize_response(self, request, response, *args, **kwargs):
|
||||||
response.request = request
|
response.request = request
|
||||||
|
|
|
@ -4,13 +4,13 @@ from decimal import Decimal
|
||||||
from django.db import models
|
from django.db import models
|
||||||
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.client import RequestFactory
|
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
from rest_framework import generics, serializers, status, filters
|
from rest_framework import generics, serializers, status, filters
|
||||||
from rest_framework.compat import django_filters, patterns, url
|
from rest_framework.compat import django_filters, patterns, url
|
||||||
|
from rest_framework.test import APIRequestFactory
|
||||||
from rest_framework.tests.models import BasicModel
|
from rest_framework.tests.models import BasicModel
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
class FilterableItem(models.Model):
|
class FilterableItem(models.Model):
|
||||||
|
|
|
@ -3,12 +3,11 @@ from django.db import models
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from rest_framework import generics, renderers, serializers, status
|
from rest_framework import generics, renderers, serializers, status
|
||||||
from rest_framework.tests.utils import RequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
from rest_framework.tests.models import BasicModel, Comment, SlugBasedModel
|
from rest_framework.tests.models import BasicModel, Comment, SlugBasedModel
|
||||||
from rest_framework.compat import six
|
from rest_framework.compat import six
|
||||||
import json
|
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
class RootView(generics.ListCreateAPIView):
|
class RootView(generics.ListCreateAPIView):
|
||||||
|
@ -71,9 +70,8 @@ class TestRootView(TestCase):
|
||||||
"""
|
"""
|
||||||
POST requests to ListCreateAPIView should create a new object.
|
POST requests to ListCreateAPIView should create a new object.
|
||||||
"""
|
"""
|
||||||
content = {'text': 'foobar'}
|
data = {'text': 'foobar'}
|
||||||
request = factory.post('/', json.dumps(content),
|
request = factory.post('/', data, format='json')
|
||||||
content_type='application/json')
|
|
||||||
with self.assertNumQueries(1):
|
with self.assertNumQueries(1):
|
||||||
response = self.view(request).render()
|
response = self.view(request).render()
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
@ -85,9 +83,8 @@ class TestRootView(TestCase):
|
||||||
"""
|
"""
|
||||||
PUT requests to ListCreateAPIView should not be allowed
|
PUT requests to ListCreateAPIView should not be allowed
|
||||||
"""
|
"""
|
||||||
content = {'text': 'foobar'}
|
data = {'text': 'foobar'}
|
||||||
request = factory.put('/', json.dumps(content),
|
request = factory.put('/', data, format='json')
|
||||||
content_type='application/json')
|
|
||||||
with self.assertNumQueries(0):
|
with self.assertNumQueries(0):
|
||||||
response = self.view(request).render()
|
response = self.view(request).render()
|
||||||
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
|
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
@ -148,9 +145,8 @@ class TestRootView(TestCase):
|
||||||
"""
|
"""
|
||||||
POST requests to create a new object should not be able to set the id.
|
POST requests to create a new object should not be able to set the id.
|
||||||
"""
|
"""
|
||||||
content = {'id': 999, 'text': 'foobar'}
|
data = {'id': 999, 'text': 'foobar'}
|
||||||
request = factory.post('/', json.dumps(content),
|
request = factory.post('/', data, format='json')
|
||||||
content_type='application/json')
|
|
||||||
with self.assertNumQueries(1):
|
with self.assertNumQueries(1):
|
||||||
response = self.view(request).render()
|
response = self.view(request).render()
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
@ -189,9 +185,8 @@ class TestInstanceView(TestCase):
|
||||||
"""
|
"""
|
||||||
POST requests to RetrieveUpdateDestroyAPIView should not be allowed
|
POST requests to RetrieveUpdateDestroyAPIView should not be allowed
|
||||||
"""
|
"""
|
||||||
content = {'text': 'foobar'}
|
data = {'text': 'foobar'}
|
||||||
request = factory.post('/', json.dumps(content),
|
request = factory.post('/', data, format='json')
|
||||||
content_type='application/json')
|
|
||||||
with self.assertNumQueries(0):
|
with self.assertNumQueries(0):
|
||||||
response = self.view(request).render()
|
response = self.view(request).render()
|
||||||
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
|
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
@ -201,9 +196,8 @@ class TestInstanceView(TestCase):
|
||||||
"""
|
"""
|
||||||
PUT requests to RetrieveUpdateDestroyAPIView should update an object.
|
PUT requests to RetrieveUpdateDestroyAPIView should update an object.
|
||||||
"""
|
"""
|
||||||
content = {'text': 'foobar'}
|
data = {'text': 'foobar'}
|
||||||
request = factory.put('/1', json.dumps(content),
|
request = factory.put('/1', data, format='json')
|
||||||
content_type='application/json')
|
|
||||||
with self.assertNumQueries(2):
|
with self.assertNumQueries(2):
|
||||||
response = self.view(request, pk='1').render()
|
response = self.view(request, pk='1').render()
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
@ -215,9 +209,8 @@ class TestInstanceView(TestCase):
|
||||||
"""
|
"""
|
||||||
PATCH requests to RetrieveUpdateDestroyAPIView should update an object.
|
PATCH requests to RetrieveUpdateDestroyAPIView should update an object.
|
||||||
"""
|
"""
|
||||||
content = {'text': 'foobar'}
|
data = {'text': 'foobar'}
|
||||||
request = factory.patch('/1', json.dumps(content),
|
request = factory.patch('/1', data, format='json')
|
||||||
content_type='application/json')
|
|
||||||
|
|
||||||
with self.assertNumQueries(2):
|
with self.assertNumQueries(2):
|
||||||
response = self.view(request, pk=1).render()
|
response = self.view(request, pk=1).render()
|
||||||
|
@ -293,9 +286,8 @@ class TestInstanceView(TestCase):
|
||||||
"""
|
"""
|
||||||
PUT requests to create a new object should not be able to set the id.
|
PUT requests to create a new object should not be able to set the id.
|
||||||
"""
|
"""
|
||||||
content = {'id': 999, 'text': 'foobar'}
|
data = {'id': 999, 'text': 'foobar'}
|
||||||
request = factory.put('/1', json.dumps(content),
|
request = factory.put('/1', data, format='json')
|
||||||
content_type='application/json')
|
|
||||||
with self.assertNumQueries(2):
|
with self.assertNumQueries(2):
|
||||||
response = self.view(request, pk=1).render()
|
response = self.view(request, pk=1).render()
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
@ -309,9 +301,8 @@ class TestInstanceView(TestCase):
|
||||||
if it does not currently exist.
|
if it does not currently exist.
|
||||||
"""
|
"""
|
||||||
self.objects.get(id=1).delete()
|
self.objects.get(id=1).delete()
|
||||||
content = {'text': 'foobar'}
|
data = {'text': 'foobar'}
|
||||||
request = factory.put('/1', json.dumps(content),
|
request = factory.put('/1', data, format='json')
|
||||||
content_type='application/json')
|
|
||||||
with self.assertNumQueries(3):
|
with self.assertNumQueries(3):
|
||||||
response = self.view(request, pk=1).render()
|
response = self.view(request, pk=1).render()
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
@ -324,10 +315,9 @@ class TestInstanceView(TestCase):
|
||||||
PUT requests to RetrieveUpdateDestroyAPIView should create an object
|
PUT requests to RetrieveUpdateDestroyAPIView should create an object
|
||||||
at the requested url if it doesn't exist.
|
at the requested url if it doesn't exist.
|
||||||
"""
|
"""
|
||||||
content = {'text': 'foobar'}
|
data = {'text': 'foobar'}
|
||||||
# pk fields can not be created on demand, only the database can set the pk for a new object
|
# pk fields can not be created on demand, only the database can set the pk for a new object
|
||||||
request = factory.put('/5', json.dumps(content),
|
request = factory.put('/5', data, format='json')
|
||||||
content_type='application/json')
|
|
||||||
with self.assertNumQueries(3):
|
with self.assertNumQueries(3):
|
||||||
response = self.view(request, pk=5).render()
|
response = self.view(request, pk=5).render()
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
@ -339,9 +329,8 @@ class TestInstanceView(TestCase):
|
||||||
PUT requests to RetrieveUpdateDestroyAPIView should create an object
|
PUT requests to RetrieveUpdateDestroyAPIView should create an object
|
||||||
at the requested url if possible, else return HTTP_403_FORBIDDEN error-response.
|
at the requested url if possible, else return HTTP_403_FORBIDDEN error-response.
|
||||||
"""
|
"""
|
||||||
content = {'text': 'foobar'}
|
data = {'text': 'foobar'}
|
||||||
request = factory.put('/test_slug', json.dumps(content),
|
request = factory.put('/test_slug', data, format='json')
|
||||||
content_type='application/json')
|
|
||||||
with self.assertNumQueries(2):
|
with self.assertNumQueries(2):
|
||||||
response = self.slug_based_view(request, slug='test_slug').render()
|
response = self.slug_based_view(request, slug='test_slug').render()
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
@ -415,9 +404,8 @@ class TestCreateModelWithAutoNowAddField(TestCase):
|
||||||
|
|
||||||
https://github.com/tomchristie/django-rest-framework/issues/285
|
https://github.com/tomchristie/django-rest-framework/issues/285
|
||||||
"""
|
"""
|
||||||
content = {'email': 'foobar@example.com', 'content': 'foobar'}
|
data = {'email': 'foobar@example.com', 'content': 'foobar'}
|
||||||
request = factory.post('/', json.dumps(content),
|
request = factory.post('/', data, format='json')
|
||||||
content_type='application/json')
|
|
||||||
response = self.view(request).render()
|
response = self.view(request).render()
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
created = self.objects.get(id=1)
|
created = self.objects.get(id=1)
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import json
|
import json
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
|
||||||
from rest_framework import generics, status, serializers
|
from rest_framework import generics, status, serializers
|
||||||
from rest_framework.compat import patterns, url
|
from rest_framework.compat import patterns, url
|
||||||
from rest_framework.tests.models import Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment, Album, Photo, OptionalRelationModel
|
from rest_framework.test import APIRequestFactory
|
||||||
|
from rest_framework.tests.models import (
|
||||||
|
Anchor, BasicModel, ManyToManyModel, BlogPost, BlogPostComment,
|
||||||
|
Album, Photo, OptionalRelationModel
|
||||||
|
)
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
class BlogPostCommentSerializer(serializers.ModelSerializer):
|
class BlogPostCommentSerializer(serializers.ModelSerializer):
|
||||||
|
@ -21,7 +24,7 @@ class BlogPostCommentSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class PhotoSerializer(serializers.Serializer):
|
class PhotoSerializer(serializers.Serializer):
|
||||||
description = serializers.CharField()
|
description = serializers.CharField()
|
||||||
album_url = serializers.HyperlinkedRelatedField(source='album', view_name='album-detail', queryset=Album.objects.all(), slug_field='title', slug_url_kwarg='title')
|
album_url = serializers.HyperlinkedRelatedField(source='album', view_name='album-detail', queryset=Album.objects.all(), lookup_field='title', slug_url_kwarg='title')
|
||||||
|
|
||||||
def restore_object(self, attrs, instance=None):
|
def restore_object(self, attrs, instance=None):
|
||||||
return Photo(**attrs)
|
return Photo(**attrs)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
|
||||||
from rest_framework.negotiation import DefaultContentNegotiation
|
from rest_framework.negotiation import DefaultContentNegotiation
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.renderers import BaseRenderer
|
from rest_framework.renderers import BaseRenderer
|
||||||
|
from rest_framework.test import APIRequestFactory
|
||||||
|
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
class MockJSONRenderer(BaseRenderer):
|
class MockJSONRenderer(BaseRenderer):
|
||||||
|
|
|
@ -4,13 +4,13 @@ from decimal import Decimal
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
from rest_framework import generics, status, pagination, filters, serializers
|
from rest_framework import generics, status, pagination, filters, serializers
|
||||||
from rest_framework.compat import django_filters
|
from rest_framework.compat import django_filters
|
||||||
|
from rest_framework.test import APIRequestFactory
|
||||||
from rest_framework.tests.models import BasicModel
|
from rest_framework.tests.models import BasicModel
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
class FilterableItem(models.Model):
|
class FilterableItem(models.Model):
|
||||||
|
@ -369,7 +369,7 @@ class TestCustomPaginationSerializer(TestCase):
|
||||||
self.page = paginator.page(1)
|
self.page = paginator.page(1)
|
||||||
|
|
||||||
def test_custom_pagination_serializer(self):
|
def test_custom_pagination_serializer(self):
|
||||||
request = RequestFactory().get('/foobar')
|
request = APIRequestFactory().get('/foobar')
|
||||||
serializer = CustomPaginationSerializer(
|
serializer = CustomPaginationSerializer(
|
||||||
instance=self.page,
|
instance=self.page,
|
||||||
context={'request': request}
|
context={'request': request}
|
||||||
|
|
|
@ -3,11 +3,10 @@ from django.contrib.auth.models import User, Permission
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from rest_framework import generics, status, permissions, authentication, HTTP_HEADER_ENCODING
|
from rest_framework import generics, status, permissions, authentication, HTTP_HEADER_ENCODING
|
||||||
from rest_framework.tests.utils import RequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
import base64
|
import base64
|
||||||
import json
|
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
class BasicModel(models.Model):
|
class BasicModel(models.Model):
|
||||||
|
@ -56,15 +55,13 @@ class ModelPermissionsIntegrationTests(TestCase):
|
||||||
BasicModel(text='foo').save()
|
BasicModel(text='foo').save()
|
||||||
|
|
||||||
def test_has_create_permissions(self):
|
def test_has_create_permissions(self):
|
||||||
request = factory.post('/', json.dumps({'text': 'foobar'}),
|
request = factory.post('/', {'text': 'foobar'}, format='json',
|
||||||
content_type='application/json',
|
|
||||||
HTTP_AUTHORIZATION=self.permitted_credentials)
|
HTTP_AUTHORIZATION=self.permitted_credentials)
|
||||||
response = root_view(request, pk=1)
|
response = root_view(request, pk=1)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
|
||||||
def test_has_put_permissions(self):
|
def test_has_put_permissions(self):
|
||||||
request = factory.put('/1', json.dumps({'text': 'foobar'}),
|
request = factory.put('/1', {'text': 'foobar'}, format='json',
|
||||||
content_type='application/json',
|
|
||||||
HTTP_AUTHORIZATION=self.permitted_credentials)
|
HTTP_AUTHORIZATION=self.permitted_credentials)
|
||||||
response = instance_view(request, pk='1')
|
response = instance_view(request, pk='1')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
@ -75,15 +72,13 @@ class ModelPermissionsIntegrationTests(TestCase):
|
||||||
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
def test_does_not_have_create_permissions(self):
|
def test_does_not_have_create_permissions(self):
|
||||||
request = factory.post('/', json.dumps({'text': 'foobar'}),
|
request = factory.post('/', {'text': 'foobar'}, format='json',
|
||||||
content_type='application/json',
|
|
||||||
HTTP_AUTHORIZATION=self.disallowed_credentials)
|
HTTP_AUTHORIZATION=self.disallowed_credentials)
|
||||||
response = root_view(request, pk=1)
|
response = root_view(request, pk=1)
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
def test_does_not_have_put_permissions(self):
|
def test_does_not_have_put_permissions(self):
|
||||||
request = factory.put('/1', json.dumps({'text': 'foobar'}),
|
request = factory.put('/1', {'text': 'foobar'}, format='json',
|
||||||
content_type='application/json',
|
|
||||||
HTTP_AUTHORIZATION=self.disallowed_credentials)
|
HTTP_AUTHORIZATION=self.disallowed_credentials)
|
||||||
response = instance_view(request, pk='1')
|
response = instance_view(request, pk='1')
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
@ -95,28 +90,26 @@ class ModelPermissionsIntegrationTests(TestCase):
|
||||||
|
|
||||||
def test_has_put_as_create_permissions(self):
|
def test_has_put_as_create_permissions(self):
|
||||||
# User only has update permissions - should be able to update an entity.
|
# User only has update permissions - should be able to update an entity.
|
||||||
request = factory.put('/1', json.dumps({'text': 'foobar'}),
|
request = factory.put('/1', {'text': 'foobar'}, format='json',
|
||||||
content_type='application/json',
|
|
||||||
HTTP_AUTHORIZATION=self.updateonly_credentials)
|
HTTP_AUTHORIZATION=self.updateonly_credentials)
|
||||||
response = instance_view(request, pk='1')
|
response = instance_view(request, pk='1')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
||||||
# But if PUTing to a new entity, permission should be denied.
|
# But if PUTing to a new entity, permission should be denied.
|
||||||
request = factory.put('/2', json.dumps({'text': 'foobar'}),
|
request = factory.put('/2', {'text': 'foobar'}, format='json',
|
||||||
content_type='application/json',
|
|
||||||
HTTP_AUTHORIZATION=self.updateonly_credentials)
|
HTTP_AUTHORIZATION=self.updateonly_credentials)
|
||||||
response = instance_view(request, pk='2')
|
response = instance_view(request, pk='2')
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
|
||||||
def test_options_permitted(self):
|
def test_options_permitted(self):
|
||||||
request = factory.options('/', content_type='application/json',
|
request = factory.options('/',
|
||||||
HTTP_AUTHORIZATION=self.permitted_credentials)
|
HTTP_AUTHORIZATION=self.permitted_credentials)
|
||||||
response = root_view(request, pk='1')
|
response = root_view(request, pk='1')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertIn('actions', response.data)
|
self.assertIn('actions', response.data)
|
||||||
self.assertEqual(list(response.data['actions'].keys()), ['POST'])
|
self.assertEqual(list(response.data['actions'].keys()), ['POST'])
|
||||||
|
|
||||||
request = factory.options('/1', content_type='application/json',
|
request = factory.options('/1',
|
||||||
HTTP_AUTHORIZATION=self.permitted_credentials)
|
HTTP_AUTHORIZATION=self.permitted_credentials)
|
||||||
response = instance_view(request, pk='1')
|
response = instance_view(request, pk='1')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
@ -124,26 +117,26 @@ class ModelPermissionsIntegrationTests(TestCase):
|
||||||
self.assertEqual(list(response.data['actions'].keys()), ['PUT'])
|
self.assertEqual(list(response.data['actions'].keys()), ['PUT'])
|
||||||
|
|
||||||
def test_options_disallowed(self):
|
def test_options_disallowed(self):
|
||||||
request = factory.options('/', content_type='application/json',
|
request = factory.options('/',
|
||||||
HTTP_AUTHORIZATION=self.disallowed_credentials)
|
HTTP_AUTHORIZATION=self.disallowed_credentials)
|
||||||
response = root_view(request, pk='1')
|
response = root_view(request, pk='1')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertNotIn('actions', response.data)
|
self.assertNotIn('actions', response.data)
|
||||||
|
|
||||||
request = factory.options('/1', content_type='application/json',
|
request = factory.options('/1',
|
||||||
HTTP_AUTHORIZATION=self.disallowed_credentials)
|
HTTP_AUTHORIZATION=self.disallowed_credentials)
|
||||||
response = instance_view(request, pk='1')
|
response = instance_view(request, pk='1')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertNotIn('actions', response.data)
|
self.assertNotIn('actions', response.data)
|
||||||
|
|
||||||
def test_options_updateonly(self):
|
def test_options_updateonly(self):
|
||||||
request = factory.options('/', content_type='application/json',
|
request = factory.options('/',
|
||||||
HTTP_AUTHORIZATION=self.updateonly_credentials)
|
HTTP_AUTHORIZATION=self.updateonly_credentials)
|
||||||
response = root_view(request, pk='1')
|
response = root_view(request, pk='1')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertNotIn('actions', response.data)
|
self.assertNotIn('actions', response.data)
|
||||||
|
|
||||||
request = factory.options('/1', content_type='application/json',
|
request = factory.options('/1',
|
||||||
HTTP_AUTHORIZATION=self.updateonly_credentials)
|
HTTP_AUTHORIZATION=self.updateonly_credentials)
|
||||||
response = instance_view(request, pk='1')
|
response = instance_view(request, pk='1')
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.compat import patterns, url
|
from rest_framework.compat import patterns, url
|
||||||
|
from rest_framework.test import APIRequestFactory
|
||||||
from rest_framework.tests.models import (
|
from rest_framework.tests.models import (
|
||||||
BlogPost,
|
BlogPost,
|
||||||
ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource,
|
ManyToManyTarget, ManyToManySource, ForeignKeyTarget, ForeignKeySource,
|
||||||
NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource
|
NullableForeignKeySource, OneToOneTarget, NullableOneToOneSource
|
||||||
)
|
)
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
request = factory.get('/') # Just to ensure we have a request in the serializer context
|
request = factory.get('/') # Just to ensure we have a request in the serializer context
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,19 +4,17 @@ from __future__ import unicode_literals
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
|
||||||
from django.utils import unittest
|
from django.utils import unittest
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from rest_framework import status, permissions
|
from rest_framework import status, permissions
|
||||||
from rest_framework.compat import yaml, etree, patterns, url, include
|
from rest_framework.compat import yaml, etree, patterns, url, include, six, StringIO
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
from rest_framework.renderers import BaseRenderer, JSONRenderer, YAMLRenderer, \
|
||||||
XMLRenderer, JSONPRenderer, BrowsableAPIRenderer, UnicodeJSONRenderer
|
XMLRenderer, JSONPRenderer, BrowsableAPIRenderer, UnicodeJSONRenderer
|
||||||
from rest_framework.parsers import YAMLParser, XMLParser
|
from rest_framework.parsers import YAMLParser, XMLParser
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
from rest_framework.compat import StringIO
|
from rest_framework.test import APIRequestFactory
|
||||||
from rest_framework.compat import six
|
|
||||||
import datetime
|
import datetime
|
||||||
import pickle
|
import pickle
|
||||||
import re
|
import re
|
||||||
|
@ -121,7 +119,7 @@ class POSTDeniedView(APIView):
|
||||||
class DocumentingRendererTests(TestCase):
|
class DocumentingRendererTests(TestCase):
|
||||||
def test_only_permitted_forms_are_displayed(self):
|
def test_only_permitted_forms_are_displayed(self):
|
||||||
view = POSTDeniedView.as_view()
|
view = POSTDeniedView.as_view()
|
||||||
request = RequestFactory().get('/')
|
request = APIRequestFactory().get('/')
|
||||||
response = view(request).render()
|
response = view(request).render()
|
||||||
self.assertNotContains(response, '>POST<')
|
self.assertNotContains(response, '>POST<')
|
||||||
self.assertContains(response, '>PUT<')
|
self.assertContains(response, '>PUT<')
|
||||||
|
|
|
@ -5,8 +5,7 @@ from __future__ import unicode_literals
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.contrib.auth import authenticate, login, logout
|
from django.contrib.auth import authenticate, login, logout
|
||||||
from django.contrib.sessions.middleware import SessionMiddleware
|
from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
from django.test import TestCase, Client
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.authentication import SessionAuthentication
|
from rest_framework.authentication import SessionAuthentication
|
||||||
from rest_framework.compat import patterns
|
from rest_framework.compat import patterns
|
||||||
|
@ -19,12 +18,13 @@ from rest_framework.parsers import (
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
from rest_framework.test import APIRequestFactory, APIClient
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.compat import six
|
from rest_framework.compat import six
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
class PlainTextParser(BaseParser):
|
class PlainTextParser(BaseParser):
|
||||||
|
@ -116,16 +116,7 @@ class TestContentParsing(TestCase):
|
||||||
Ensure request.DATA returns content for PUT request with form content.
|
Ensure request.DATA returns content for PUT request with form content.
|
||||||
"""
|
"""
|
||||||
data = {'qwerty': 'uiop'}
|
data = {'qwerty': 'uiop'}
|
||||||
|
request = Request(factory.put('/', data))
|
||||||
from django import VERSION
|
|
||||||
|
|
||||||
if VERSION >= (1, 5):
|
|
||||||
from django.test.client import MULTIPART_CONTENT, BOUNDARY, encode_multipart
|
|
||||||
request = Request(factory.put('/', encode_multipart(BOUNDARY, data),
|
|
||||||
content_type=MULTIPART_CONTENT))
|
|
||||||
else:
|
|
||||||
request = Request(factory.put('/', data))
|
|
||||||
|
|
||||||
request.parsers = (FormParser(), MultiPartParser())
|
request.parsers = (FormParser(), MultiPartParser())
|
||||||
self.assertEqual(list(request.DATA.items()), list(data.items()))
|
self.assertEqual(list(request.DATA.items()), list(data.items()))
|
||||||
|
|
||||||
|
@ -257,7 +248,7 @@ class TestContentParsingWithAuthentication(TestCase):
|
||||||
urls = 'rest_framework.tests.test_request'
|
urls = 'rest_framework.tests.test_request'
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.csrf_client = Client(enforce_csrf_checks=True)
|
self.csrf_client = APIClient(enforce_csrf_checks=True)
|
||||||
self.username = 'john'
|
self.username = 'john'
|
||||||
self.email = 'lennon@thebeatles.com'
|
self.email = 'lennon@thebeatles.com'
|
||||||
self.password = 'password'
|
self.password = 'password'
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
|
||||||
from rest_framework.compat import patterns, url
|
from rest_framework.compat import patterns, url
|
||||||
from rest_framework.reverse import reverse
|
from rest_framework.reverse import reverse
|
||||||
|
from rest_framework.test import APIRequestFactory
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
def null_view(request):
|
def null_view(request):
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from rest_framework import serializers, viewsets, permissions
|
from rest_framework import serializers, viewsets, permissions
|
||||||
from rest_framework.compat import include, patterns, url
|
from rest_framework.compat import include, patterns, url
|
||||||
from rest_framework.decorators import link, action
|
from rest_framework.decorators import link, action
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.routers import SimpleRouter, DefaultRouter
|
from rest_framework.routers import SimpleRouter, DefaultRouter
|
||||||
|
from rest_framework.test import APIRequestFactory
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
urlpatterns = patterns('',)
|
urlpatterns = patterns('',)
|
||||||
|
|
||||||
|
@ -193,6 +193,7 @@ class TestActionKeywordArgs(TestCase):
|
||||||
{'permission_classes': [permissions.AllowAny]}
|
{'permission_classes': [permissions.AllowAny]}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestActionAppliedToExistingRoute(TestCase):
|
class TestActionAppliedToExistingRoute(TestCase):
|
||||||
"""
|
"""
|
||||||
Ensure `@action` decorator raises an except when applied
|
Ensure `@action` decorator raises an except when applied
|
||||||
|
|
|
@ -494,7 +494,7 @@ class CustomValidationTests(TestCase):
|
||||||
}
|
}
|
||||||
serializer = self.CommentSerializerWithFieldValidator(data=wrong_data)
|
serializer = self.CommentSerializerWithFieldValidator(data=wrong_data)
|
||||||
self.assertFalse(serializer.is_valid())
|
self.assertFalse(serializer.is_valid())
|
||||||
self.assertEqual(serializer.errors, {'email': ['Enter a valid e-mail address.']})
|
self.assertEqual(serializer.errors, {'email': ['Enter a valid email address.']})
|
||||||
|
|
||||||
|
|
||||||
class PositiveIntegerAsChoiceTests(TestCase):
|
class PositiveIntegerAsChoiceTests(TestCase):
|
||||||
|
@ -1376,6 +1376,18 @@ class FieldLabelTest(TestCase):
|
||||||
self.assertEqual('Label', relations.HyperlinkedRelatedField(view_name='fake', label='Label', help_text='Help', many=True).label)
|
self.assertEqual('Label', relations.HyperlinkedRelatedField(view_name='fake', label='Label', help_text='Help', many=True).label)
|
||||||
|
|
||||||
|
|
||||||
|
# Test for issue #961
|
||||||
|
|
||||||
|
class ManyFieldHelpTextTest(TestCase):
|
||||||
|
def test_help_text_no_hold_down_control_msg(self):
|
||||||
|
"""
|
||||||
|
Validate that help_text doesn't contain the 'Hold down "Control" ...'
|
||||||
|
message that Django appends to choice fields.
|
||||||
|
"""
|
||||||
|
rel_field = fields.Field(help_text=ManyToManyModel._meta.get_field('rel').help_text)
|
||||||
|
self.assertEqual('Some help text.', rel_field.help_text)
|
||||||
|
|
||||||
|
|
||||||
class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
|
class AttributeMappingOnAutogeneratedFieldsTests(TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
115
rest_framework/tests/test_testing.py
Normal file
115
rest_framework/tests/test_testing.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
# -- coding: utf-8 --
|
||||||
|
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.test import TestCase
|
||||||
|
from rest_framework.compat import patterns, url
|
||||||
|
from rest_framework.decorators import api_view
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.test import APIClient, APIRequestFactory, force_authenticate
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['GET', 'POST'])
|
||||||
|
def view(request):
|
||||||
|
return Response({
|
||||||
|
'auth': request.META.get('HTTP_AUTHORIZATION', b''),
|
||||||
|
'user': request.user.username
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^view/$', view),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAPITestClient(TestCase):
|
||||||
|
urls = 'rest_framework.tests.test_testing'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.client = APIClient()
|
||||||
|
|
||||||
|
def test_credentials(self):
|
||||||
|
"""
|
||||||
|
Setting `.credentials()` adds the required headers to each request.
|
||||||
|
"""
|
||||||
|
self.client.credentials(HTTP_AUTHORIZATION='example')
|
||||||
|
for _ in range(0, 3):
|
||||||
|
response = self.client.get('/view/')
|
||||||
|
self.assertEqual(response.data['auth'], 'example')
|
||||||
|
|
||||||
|
def test_force_authenticate(self):
|
||||||
|
"""
|
||||||
|
Setting `.force_authenticate()` forcibly authenticates each request.
|
||||||
|
"""
|
||||||
|
user = User.objects.create_user('example', 'example@example.com')
|
||||||
|
self.client.force_authenticate(user)
|
||||||
|
response = self.client.get('/view/')
|
||||||
|
self.assertEqual(response.data['user'], 'example')
|
||||||
|
|
||||||
|
def test_csrf_exempt_by_default(self):
|
||||||
|
"""
|
||||||
|
By default, the test client is CSRF exempt.
|
||||||
|
"""
|
||||||
|
User.objects.create_user('example', 'example@example.com', 'password')
|
||||||
|
self.client.login(username='example', password='password')
|
||||||
|
response = self.client.post('/view/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_explicitly_enforce_csrf_checks(self):
|
||||||
|
"""
|
||||||
|
The test client can enforce CSRF checks.
|
||||||
|
"""
|
||||||
|
client = APIClient(enforce_csrf_checks=True)
|
||||||
|
User.objects.create_user('example', 'example@example.com', 'password')
|
||||||
|
client.login(username='example', password='password')
|
||||||
|
response = client.post('/view/')
|
||||||
|
expected = {'detail': 'CSRF Failed: CSRF cookie not set.'}
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
self.assertEqual(response.data, expected)
|
||||||
|
|
||||||
|
|
||||||
|
class TestAPIRequestFactory(TestCase):
|
||||||
|
def test_csrf_exempt_by_default(self):
|
||||||
|
"""
|
||||||
|
By default, the test client is CSRF exempt.
|
||||||
|
"""
|
||||||
|
user = User.objects.create_user('example', 'example@example.com', 'password')
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
request = factory.post('/view/')
|
||||||
|
request.user = user
|
||||||
|
response = view(request)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_explicitly_enforce_csrf_checks(self):
|
||||||
|
"""
|
||||||
|
The test client can enforce CSRF checks.
|
||||||
|
"""
|
||||||
|
user = User.objects.create_user('example', 'example@example.com', 'password')
|
||||||
|
factory = APIRequestFactory(enforce_csrf_checks=True)
|
||||||
|
request = factory.post('/view/')
|
||||||
|
request.user = user
|
||||||
|
response = view(request)
|
||||||
|
expected = {'detail': 'CSRF Failed: CSRF cookie not set.'}
|
||||||
|
self.assertEqual(response.status_code, 403)
|
||||||
|
self.assertEqual(response.data, expected)
|
||||||
|
|
||||||
|
def test_invalid_format(self):
|
||||||
|
"""
|
||||||
|
Attempting to use a format that is not configured will raise an
|
||||||
|
assertion error.
|
||||||
|
"""
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
self.assertRaises(AssertionError, factory.post,
|
||||||
|
path='/view/', data={'example': 1}, format='xml'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_force_authenticate(self):
|
||||||
|
"""
|
||||||
|
Setting `force_authenticate()` forcibly authenticates the request.
|
||||||
|
"""
|
||||||
|
user = User.objects.create_user('example', 'example@example.com')
|
||||||
|
factory = APIRequestFactory()
|
||||||
|
request = factory.get('/view')
|
||||||
|
force_authenticate(request, user=user)
|
||||||
|
response = view(request)
|
||||||
|
self.assertEqual(response.data['user'], 'example')
|
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.test.client import RequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.throttling import UserRateThrottle, ScopedRateThrottle
|
from rest_framework.throttling import UserRateThrottle, ScopedRateThrottle
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
@ -41,7 +41,7 @@ class ThrottlingTests(TestCase):
|
||||||
Reset the cache so that no throttles will be active
|
Reset the cache so that no throttles will be active
|
||||||
"""
|
"""
|
||||||
cache.clear()
|
cache.clear()
|
||||||
self.factory = RequestFactory()
|
self.factory = APIRequestFactory()
|
||||||
|
|
||||||
def test_requests_are_throttled(self):
|
def test_requests_are_throttled(self):
|
||||||
"""
|
"""
|
||||||
|
@ -173,7 +173,7 @@ class ScopedRateThrottleTests(TestCase):
|
||||||
return Response('y')
|
return Response('y')
|
||||||
|
|
||||||
self.throttle_class = XYScopedRateThrottle
|
self.throttle_class = XYScopedRateThrottle
|
||||||
self.factory = RequestFactory()
|
self.factory = APIRequestFactory()
|
||||||
self.x_view = XView.as_view()
|
self.x_view = XView.as_view()
|
||||||
self.y_view = YView.as_view()
|
self.y_view = YView.as_view()
|
||||||
self.unscoped_view = UnscopedView.as_view()
|
self.unscoped_view = UnscopedView.as_view()
|
||||||
|
|
|
@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from django.core import urlresolvers
|
from django.core import urlresolvers
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
from rest_framework.compat import patterns, url, include
|
from rest_framework.compat import patterns, url, include
|
||||||
from rest_framework.urlpatterns import format_suffix_patterns
|
from rest_framework.urlpatterns import format_suffix_patterns
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ class FormatSuffixTests(TestCase):
|
||||||
Tests `format_suffix_patterns` against different URLPatterns to ensure the URLs still resolve properly, including any captured parameters.
|
Tests `format_suffix_patterns` against different URLPatterns to ensure the URLs still resolve properly, including any captured parameters.
|
||||||
"""
|
"""
|
||||||
def _resolve_urlpatterns(self, urlpatterns, test_paths):
|
def _resolve_urlpatterns(self, urlpatterns, test_paths):
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
try:
|
try:
|
||||||
urlpatterns = format_suffix_patterns(urlpatterns)
|
urlpatterns = format_suffix_patterns(urlpatterns)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
@ -2,10 +2,9 @@ from __future__ import unicode_literals
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from rest_framework import generics, serializers, status
|
from rest_framework import generics, serializers, status
|
||||||
from rest_framework.tests.utils import RequestFactory
|
from rest_framework.test import APIRequestFactory
|
||||||
import json
|
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
# Regression for #666
|
# Regression for #666
|
||||||
|
@ -33,8 +32,7 @@ class TestPreSaveValidationExclusions(TestCase):
|
||||||
validation on read only fields.
|
validation on read only fields.
|
||||||
"""
|
"""
|
||||||
obj = ValidationModel.objects.create(blank_validated_field='')
|
obj = ValidationModel.objects.create(blank_validated_field='')
|
||||||
request = factory.put('/', json.dumps({}),
|
request = factory.put('/', {}, format='json')
|
||||||
content_type='application/json')
|
|
||||||
view = UpdateValidationModel().as_view()
|
view = UpdateValidationModel().as_view()
|
||||||
response = view(request, pk=obj.pk).render()
|
response = view(request, pk=obj.pk).render()
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.test.client import RequestFactory
|
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.decorators import api_view
|
from rest_framework.decorators import api_view
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.settings import api_settings
|
from rest_framework.settings import api_settings
|
||||||
|
from rest_framework.test import APIRequestFactory
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
|
||||||
factory = RequestFactory()
|
factory = APIRequestFactory()
|
||||||
|
|
||||||
|
|
||||||
class BasicView(APIView):
|
class BasicView(APIView):
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
from __future__ import unicode_literals
|
|
||||||
from django.test.client import FakePayload, Client as _Client, RequestFactory as _RequestFactory
|
|
||||||
from django.test.client import MULTIPART_CONTENT
|
|
||||||
from rest_framework.compat import urlparse
|
|
||||||
|
|
||||||
|
|
||||||
class RequestFactory(_RequestFactory):
|
|
||||||
|
|
||||||
def __init__(self, **defaults):
|
|
||||||
super(RequestFactory, self).__init__(**defaults)
|
|
||||||
|
|
||||||
def patch(self, path, data={}, content_type=MULTIPART_CONTENT,
|
|
||||||
**extra):
|
|
||||||
"Construct a PATCH request."
|
|
||||||
|
|
||||||
patch_data = self._encode_data(data, content_type)
|
|
||||||
|
|
||||||
parsed = urlparse.urlparse(path)
|
|
||||||
r = {
|
|
||||||
'CONTENT_LENGTH': len(patch_data),
|
|
||||||
'CONTENT_TYPE': content_type,
|
|
||||||
'PATH_INFO': self._get_path(parsed),
|
|
||||||
'QUERY_STRING': parsed[4],
|
|
||||||
'REQUEST_METHOD': 'PATCH',
|
|
||||||
'wsgi.input': FakePayload(patch_data),
|
|
||||||
}
|
|
||||||
r.update(extra)
|
|
||||||
return self.request(**r)
|
|
||||||
|
|
||||||
|
|
||||||
class Client(_Client, RequestFactory):
|
|
||||||
def patch(self, path, data={}, content_type=MULTIPART_CONTENT,
|
|
||||||
follow=False, **extra):
|
|
||||||
"""
|
|
||||||
Send a resource to the server using PATCH.
|
|
||||||
"""
|
|
||||||
response = super(Client, self).patch(path, data=data, content_type=content_type, **extra)
|
|
||||||
if follow:
|
|
||||||
response = self._handle_redirects(response, **extra)
|
|
||||||
return response
|
|
1
setup.py
1
setup.py
|
@ -55,7 +55,6 @@ setup(
|
||||||
name='djangorestframework',
|
name='djangorestframework',
|
||||||
version=version,
|
version=version,
|
||||||
url='http://django-rest-framework.org',
|
url='http://django-rest-framework.org',
|
||||||
download_url='http://pypi.python.org/pypi/rest_framework/',
|
|
||||||
license='BSD',
|
license='BSD',
|
||||||
description='Web APIs for Django, made easy.',
|
description='Web APIs for Django, made easy.',
|
||||||
author='Tom Christie',
|
author='Tom Christie',
|
||||||
|
|
Loading…
Reference in New Issue
Block a user