Merge branch 'master' into docs

This commit is contained in:
Tom Christie 2017-01-05 10:53:43 +00:00
commit 98936655f4
59 changed files with 666 additions and 573 deletions

View File

@ -23,7 +23,7 @@ The initial aim is to provide a single full-time position on REST framework.
<p align="center">
<a href="http://jobs.rover.com/"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/rover-readme.png"/></a>
<a href="https://getsentry.com/welcome/"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/sentry-readme.png"/></a>
<a href="https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/stream-readme.png"/></a>
<a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/stream-readme.png"/></a>
<a href="http://www.machinalis.com/#services"><img src="https://raw.githubusercontent.com/tomchristie/django-rest-framework/master/docs/img/premium/machinalis-readme.png"/></a>
</p>

View File

@ -363,7 +363,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a
[oauth]: http://oauth.net/2/
[permission]: permissions.md
[throttling]: throttling.md
[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax
[mod_wsgi_official]: http://code.google.com/p/modwsgi/wiki/ConfigurationDirectives#WSGIPassAuthorization
[django-oauth-toolkit-getting-started]: https://django-oauth-toolkit.readthedocs.io/en/latest/rest-framework/getting_started.html
[django-rest-framework-oauth]: http://jpadilla.github.io/django-rest-framework-oauth/

View File

@ -261,7 +261,7 @@ Corresponds to `django.db.models.fields.DecimalField`.
**Signature**: `DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)`
- `max_digits` The maximum number of digits allowed in the number. Note that this number must be greater than or equal to decimal_places.
- `max_digits` The maximum number of digits allowed in the number. It must be either `None` or an integer greater than or equal to `decimal_places`.
- `decimal_places` The number of decimal places to store with the number.
- `coerce_to_string` Set to `True` if string values should be returned for the representation, or `False` if `Decimal` objects should be returned. Defaults to the same value as the `COERCE_DECIMAL_TO_STRING` settings key, which will be `True` unless overridden. If `Decimal` objects are returned by the serializer, then the final output format will be determined by the renderer. Note that setting `localize` will force the value to `True`.
- `max_value` Validate that the number provided is no greater than this value.
@ -665,12 +665,12 @@ The [django-rest-framework-gis][django-rest-framework-gis] package provides geog
The [django-rest-framework-hstore][django-rest-framework-hstore] package provides an `HStoreField` to support [django-hstore][django-hstore] `DictionaryField` model field.
[cite]: https://docs.djangoproject.com/en/dev/ref/forms/api/#django.forms.Form.cleaned_data
[cite]: https://docs.djangoproject.com/en/stable/ref/forms/api/#django.forms.Form.cleaned_data
[html-and-forms]: ../topics/html-and-forms.md
[FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS
[FILE_UPLOAD_HANDLERS]: https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-FILE_UPLOAD_HANDLERS
[ecma262]: http://ecma-international.org/ecma-262/5.1/#sec-15.9.1.15
[strftime]: http://docs.python.org/2/library/datetime.html#strftime-and-strptime-behavior
[django-widgets]: https://docs.djangoproject.com/en/dev/ref/forms/widgets/
[strftime]: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior
[django-widgets]: https://docs.djangoproject.com/en/stable/ref/forms/widgets/
[iso8601]: http://www.w3.org/TR/NOTE-datetime
[drf-compound-fields]: https://drf-compound-fields.readthedocs.io
[drf-extra-fields]: https://github.com/Hipo/drf-extra-fields

View File

@ -455,14 +455,14 @@ The [djangorestframework-word-filter][django-rest-framework-word-search-filter]
[drf-url-filter][drf-url-filter] is a simple Django app to apply filters on drf `ModelViewSet`'s `Queryset` in a clean, simple and configurable way. It also supports validations on incoming query params and their values. A beautiful python package `Voluptuous` is being used for validations on the incoming query parameters. The best part about voluptuous is you can define your own validations as per your query params requirements.
[cite]: https://docs.djangoproject.com/en/dev/topics/db/queries/#retrieving-specific-objects-with-filters
[cite]: https://docs.djangoproject.com/en/stable/topics/db/queries/#retrieving-specific-objects-with-filters
[django-filter]: https://github.com/alex/django-filter
[django-filter-docs]: https://django-filter.readthedocs.io/en/latest/index.html
[guardian]: https://django-guardian.readthedocs.io/
[view-permissions]: https://django-guardian.readthedocs.io/en/latest/userguide/assign.html
[view-permissions-blogpost]: http://blog.nyaruka.com/adding-a-view-permission-to-django-models
[nullbooleanselect]: https://github.com/django/django/blob/master/django/forms/widgets.py
[search-django-admin]: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
[search-django-admin]: https://docs.djangoproject.com/en/stable/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields
[django-rest-framework-filters]: https://github.com/philipn/django-rest-framework-filters
[django-rest-framework-word-search-filter]: https://github.com/trollknurr/django-rest-framework-word-search-filter
[django-url-filter]: https://github.com/miki725/django-url-filter

View File

@ -382,7 +382,7 @@ The [django-rest-framework-bulk package][django-rest-framework-bulk] implements
[Django Rest Multiple Models][django-rest-multiple-models] provides a generic view (and mixin) for sending multiple serialized models and/or querysets via a single API request.
[cite]: https://docs.djangoproject.com/en/dev/ref/class-based-views/#base-vs-generic-views
[cite]: https://docs.djangoproject.com/en/stable/ref/class-based-views/#base-vs-generic-views
[GenericAPIView]: #genericapiview
[ListModelMixin]: #listmodelmixin
[CreateModelMixin]: #createmodelmixin

View File

@ -104,6 +104,18 @@ Then configure your settings to use this custom class:
'DEFAULT_METADATA_CLASS': 'myproject.apps.core.MinimalMetadata'
}
# Third party packages
The following third party packages provide additional metadata implementations.
## DRF-schema-adapter
[drf-schema-adapter][drf-schema-adapter] is a set of tools that makes it easier to provide schema information to frontend frameworks and libraries. It provides a metadata mixin as well as 2 metadata classes and several adapters suitable to generate [json-schema][json-schema] as well as schema information readable by various libraries.
You can also write your own adapter to work with your specific frontend.
If you whish to do so, it also provides an exporter that can export those schema information to json files.
[cite]: http://tools.ietf.org/html/rfc7231#section-4.3.7
[no-options]: https://www.mnot.net/blog/2012/10/29/NO_OPTIONS
[json-schema]: http://json-schema.org/
[drf-schema-adapter]: https://github.com/drf-forms/drf-schema-adapter

View File

@ -325,7 +325,7 @@ The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin`
The [`drf-proxy-pagination` package][drf-proxy-pagination] includes a `ProxyPagination` class which allows to choose pagination class with a query parameter.
[cite]: https://docs.djangoproject.com/en/dev/topics/pagination/
[cite]: https://docs.djangoproject.com/en/stable/topics/pagination/
[github-link-pagination]: https://developer.github.com/guides/traversing-with-pagination/
[link-header]: ../img/link-header-pagination.png
[drf-extensions]: http://chibisov.github.io/drf-extensions/docs/

View File

@ -224,7 +224,7 @@ Modify your REST framework settings.
[jquery-ajax]: http://api.jquery.com/jQuery.ajax/
[cite]: https://groups.google.com/d/topic/django-developers/dxI4qVzrBY4/discussion
[upload-handlers]: https://docs.djangoproject.com/en/dev/topics/http/file-uploads/#upload-handlers
[upload-handlers]: https://docs.djangoproject.com/en/stable/topics/http/file-uploads/#upload-handlers
[rest-framework-yaml]: http://jpadilla.github.io/django-rest-framework-yaml/
[rest-framework-xml]: http://jpadilla.github.io/django-rest-framework-xml/
[yaml]: http://www.yaml.org/

View File

@ -164,7 +164,7 @@ As with `DjangoModelPermissions`, this permission must only be applied to views
Note that `DjangoObjectPermissions` **does not** require the `django-guardian` package, and should support other object-level backends equally well.
As with `DjangoModelPermissions` you can use custom model permissions by overriding `DjangoModelPermissions` and setting the `.perms_map` property. Refer to the source code for details.
As with `DjangoModelPermissions` you can use custom model permissions by overriding `DjangoObjectPermissions` and setting the `.perms_map` property. Refer to the source code for details.
---
@ -269,8 +269,8 @@ The [Django Rest Framework Roles][django-rest-framework-roles] package makes it
[authentication]: authentication.md
[throttling]: throttling.md
[filtering]: filtering.md
[contribauth]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#custom-permissions
[objectpermissions]: https://docs.djangoproject.com/en/dev/topics/auth/customizing/#handling-object-permissions
[contribauth]: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#custom-permissions
[objectpermissions]: https://docs.djangoproject.com/en/stable/topics/auth/customizing/#handling-object-permissions
[guardian]: https://github.com/lukaszb/django-guardian
[get_objects_for_user]: http://pythonhosted.org/django-guardian/api/guardian.shortcuts.html#get-objects-for-user
[2.2-announcement]: ../topics/2.2-announcement.md

View File

@ -505,7 +505,7 @@ For example, given the following model for a tag, which has a generic relationsh
"""
Tags arbitrary model instances using a generic relation.
See: https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/
See: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/
"""
tag_name = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
@ -593,9 +593,9 @@ The [drf-nested-routers package][drf-nested-routers] provides routers and relati
The [rest-framework-generic-relations][drf-nested-relations] library provides read/write serialization for generic foreign keys.
[cite]: http://lwn.net/Articles/193245/
[reverse-relationships]: https://docs.djangoproject.com/en/dev/topics/db/queries/#following-relationships-backward
[reverse-relationships]: https://docs.djangoproject.com/en/stable/topics/db/queries/#following-relationships-backward
[routers]: http://www.django-rest-framework.org/api-guide/routers#defaultrouter
[generic-relations]: https://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/#id1
[generic-relations]: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#id1
[2.2-announcement]: ../topics/2.2-announcement.md
[drf-nested-routers]: https://github.com/alanjds/drf-nested-routers
[drf-nested-relations]: https://github.com/Ian-Foote/rest-framework-generic-relations

View File

@ -123,6 +123,8 @@ You can use `TemplateHTMLRenderer` either to return regular HTML pages using RES
If you're building websites that use `TemplateHTMLRenderer` along with other renderer classes, you should consider listing `TemplateHTMLRenderer` as the first class in the `renderer_classes` list, so that it will be prioritised first even for browsers that send poorly formed `ACCEPT:` headers.
See the [_HTML & Forms_ Topic Page][html-and-forms] for further examples of `TemplateHTMLRenderer` usage.
**.media_type**: `text/html`
**.format**: `'.html'`
@ -476,7 +478,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[Rest Framework Latex] provides a renderer that outputs PDFs using Laulatex. It is maintained by [Pebble (S/F Software)][mypebble].
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/#the-rendering-process
[cite]: https://docs.djangoproject.com/en/stable/stable/template-response/#the-rendering-process
[conneg]: content-negotiation.md
[html-and-forms]: ../topics/html-and-forms.md
[browser-accept-headers]: http://www.gethifi.com/blog/browser-rest-http-accept-headers
@ -485,7 +487,7 @@ Comma-separated values are a plain-text tabular data format, that can be easily
[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.collection+json]: http://www.amundsen.com/media-types/collection/
[django-error-views]: https://docs.djangoproject.com/en/dev/topics/http/views/#customizing-error-views
[django-error-views]: https://docs.djangoproject.com/en/stable/topics/http/views/#customizing-error-views
[rest-framework-jsonp]: http://jpadilla.github.io/django-rest-framework-jsonp/
[cors]: http://www.w3.org/TR/cors/
[cors-docs]: http://www.django-rest-framework.org/topics/ajax-csrf-cors/

View File

@ -91,5 +91,5 @@ As with any other `TemplateResponse`, this method is called to render the serial
You won't typically need to call `.render()` yourself, as it's handled by Django's standard response cycle.
[cite]: https://docs.djangoproject.com/en/dev/ref/template-response/
[cite]: https://docs.djangoproject.com/en/stable/stable/template-response/
[statuscodes]: status-codes.md

View File

@ -51,5 +51,5 @@ As with the `reverse` function, you should **include the request as a keyword ar
api_root = reverse_lazy('api-root', request=request)
[cite]: http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm#sec_5_1_5
[reverse]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse
[reverse-lazy]: https://docs.djangoproject.com/en/dev/topics/http/urls/#reverse-lazy
[reverse]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse
[reverse-lazy]: https://docs.djangoproject.com/en/stable/topics/http/urls/#reverse-lazy

View File

@ -281,8 +281,8 @@ A generic view with sections in the class docstring, using single-line style.
class UserList(generics.ListCreateAPIView):
"""
get: Create a new user.
post: List all the users.
get: List all the users.
post: Create a new user.
"""
queryset = User.objects.all()
serializer_class = UserSerializer
@ -541,5 +541,5 @@ A short description of the meaning and intended usage of the input field.
[open-api]: https://openapis.org/
[json-hyperschema]: http://json-schema.org/latest/json-schema-hypermedia.html
[api-blueprint]: https://apiblueprint.org/
[static-files]: https://docs.djangoproject.com/en/dev/howto/static-files/
[named-arguments]: https://docs.djangoproject.com/en/dev/topics/http/urls/#named-groups
[static-files]: https://docs.djangoproject.com/en/stable/howto/static-files/
[named-arguments]: https://docs.djangoproject.com/en/stable/topics/http/urls/#named-groups

View File

@ -578,16 +578,6 @@ Alternative representations include serializing using hyperlinks, serializing co
For full details see the [serializer relations][relations] documentation.
## Inheritance of the 'Meta' class
The inner `Meta` class on serializers is not inherited from parent classes by default. This is the same behavior as with Django's `Model` and `ModelForm` classes. If you want the `Meta` class to inherit from a parent class you must do so explicitly. For example:
class AccountSerializer(MyBaseSerializer):
class Meta(MyBaseSerializer.Meta):
model = Account
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
## Customizing field mappings
The ModelSerializer class also exposes an API that you can override in order to alter how serializer fields are automatically determined when instantiating the serializer.
@ -1025,6 +1015,40 @@ If any of the validation fails, then the method should raise a `serializers.Vali
The `data` argument passed to this method will normally be the value of `request.data`, so the datatype it provides will depend on the parser classes you have configured for your API.
## Serializer Inheritance
Similar to Django forms, you can extend and reuse serializers through inheritance. This allows you to declare a common set of fields or methods on a parent class that can then be used in a number of serializers. For example,
class MyBaseSerializer(Serializer):
my_field = serializers.CharField()
def validate_my_field(self):
...
class MySerializer(MyBaseSerializer):
...
Like Django's `Model` and `ModelForm` classes, the inner `Meta` class on serializers does not implicitly inherit from it's parents' inner `Meta` classes. If you want the `Meta` class to inherit from a parent class you must do so explicitly. For example:
class AccountSerializer(MyBaseSerializer):
class Meta(MyBaseSerializer.Meta):
model = Account
Typically we would recommend *not* using inheritance on inner Meta classes, but instead declaring all options explicitly.
Additionally, the following caveats apply to serializer inheritance:
* Normal Python name resolution rules apply. If you have multiple base classes that declare a `Meta` inner class, only the first one will be used. This means the childs `Meta`, if it exists, otherwise the `Meta` of the first parent, etc.
* Its possible to declaratively remove a `Field` inherited from a parent class by setting the name to be `None` on the subclass.
class MyBaseSerializer(ModelSerializer):
my_field = serializers.CharField()
class MySerializer(MyBaseSerializer):
my_field = None
However, you can only use this technique to opt out from a field defined declaratively by a parent class; it wont prevent the `ModelSerializer` from generating a default field. To opt-out from default fields, see [Specifying which fields to include](#specifying-which-fields-to-include).
## Dynamically modifying 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.
@ -1112,13 +1136,28 @@ The [dynamic-rest][dynamic-rest] package extends the ModelSerializer and ModelVi
The [drf-dynamic-fields][drf-dynamic-fields] package provides a mixin to dynamically limit the fields per serializer to a subset specified by an URL parameter.
## DRF FlexFields
The [drf-flex-fields][drf-flex-fields] package extends the ModelSerializer and ModelViewSet to provide commonly used functionality for dynamically setting fields and expanding primitive fields to nested models, both from URL parameters and your serializer class definitions.
## Serializer Extensions
The [django-rest-framework-serializer-extensions][drf-serializer-extensions]
package provides a collection of tools to DRY up your serializers, by allowing
fields to be defined on a per-view/request basis. Fields can be whitelisted,
blacklisted and child serializers can be optionally expanded.
## HTML JSON Forms
The [html-json-forms][html-json-forms] package provides an algorithm and serializer for processing `<form>` submissions per the (inactive) [HTML JSON Form specification][json-form-spec]. The serializer facilitates processing of arbitrarily nested JSON structures within HTML. For example, `<input name="items[0][id]" value="5">` will be interpreted as `{"items": [{"id": "5"}]}`.
## DRF-Base64
[DRF-Base64][drf-base64] provides a set of field and model serializers that handles the upload of base64-encoded files.
[cite]: https://groups.google.com/d/topic/django-users/sVFaOfQi4wY/discussion
[relations]: relations.md
[model-managers]: https://docs.djangoproject.com/en/dev/topics/db/managers/
[model-managers]: https://docs.djangoproject.com/en/stable/topics/db/managers/
[encapsulation-blogpost]: http://www.dabapps.com/blog/django-models-and-encapsulation/
[django-rest-marshmallow]: http://tomchristie.github.io/django-rest-marshmallow/
[marshmallow]: https://marshmallow.readthedocs.io/en/latest/
@ -1129,5 +1168,8 @@ The [html-json-forms][html-json-forms] package provides an algorithm and seriali
[django-hstore]: https://github.com/djangonauts/django-hstore
[dynamic-rest]: https://github.com/AltSchool/dynamic-rest
[html-json-forms]: https://github.com/wq/html-json-forms
[drf-flex-fields]: https://github.com/rsinger86/drf-flex-fields
[json-form-spec]: https://www.w3.org/TR/html-json-forms/
[drf-dynamic-fields]: https://github.com/dbrgn/drf-dynamic-fields
[drf-base64]: https://bitbucket.org/levit_scs/drf_base64
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions

View File

@ -456,7 +456,7 @@ An integer of 0 or more, that may be used to specify the number of application p
Default: `None`
[cite]: http://www.python.org/dev/peps/pep-0020/
[cite]: https://www.python.org/dev/peps/pep-0020/
[rfc4627]: http://www.ietf.org/rfc/rfc4627.txt
[heroku-minified-json]: https://github.com/interagent/http-api-design#keep-json-minified-in-all-responses
[strftime]: http://docs.python.org/2/library/time.html#time.strftime
[strftime]: https://docs.python.org/3/library/time.html#time.strftime

View File

@ -205,7 +205,9 @@ Note that the requests client requires you to pass fully qualified URLs.
## `RequestsClient` and working with the database
The `RequestsClient` class is useful if
The `RequestsClient` class is useful if you want to write tests that solely interact with the service interface. This is a little stricter than using the standard Django test client, as it means that all interactions should be via the API.
If you're using `RequestsClient` you'll want to ensure that test setup, and results assertions are performed as regular API calls, rather than interacting with the database models directly. For example, rather than checking that `Customer.objects.count() == 3` you would list the customers endpoint, and ensure that it contains three records.
## Headers & Authentication
@ -373,6 +375,6 @@ For example, to add support for using `format='html'` in test requests, you migh
}
[cite]: http://jacobian.org/writing/django-apps-with-buildout/#s-create-a-test-wrapper
[client]: https://docs.djangoproject.com/en/dev/topics/testing/tools/#the-test-client
[requestfactory]: https://docs.djangoproject.com/en/dev/topics/testing/advanced/#django.test.client.RequestFactory
[client]: https://docs.djangoproject.com/en/stable/topics/testing/tools/#the-test-client
[requestfactory]: https://docs.djangoproject.com/en/stable/topics/testing/advanced/#django.test.client.RequestFactory
[configuration]: #configuration

View File

@ -193,5 +193,5 @@ The following is an example of a rate throttle, that will randomly throttle 1 in
[cite]: https://dev.twitter.com/docs/error-codes-responses
[permissions]: permissions.md
[identifing-clients]: http://oxpedia.org/wiki/index.php?title=AppSuite:Grizzly#Multiple_Proxies_in_front_of_the_cluster
[cache-setting]: https://docs.djangoproject.com/en/dev/ref/settings/#caches
[cache-docs]: https://docs.djangoproject.com/en/dev/topics/cache/#setting-up-the-cache
[cache-setting]: https://docs.djangoproject.com/en/stable/ref/settings/#caches
[cache-docs]: https://docs.djangoproject.com/en/stable/topics/cache/#setting-up-the-cache

View File

@ -300,4 +300,4 @@ In some advanced cases you might want a validator to be passed the serializer fi
# In `__call__` we can then use that information to modify the validation behavior.
self.is_update = serializer_field.parent.instance is not None
[cite]: https://docs.djangoproject.com/en/dev/ref/validators/
[cite]: https://docs.djangoproject.com/en/stable/ref/validators/

View File

@ -73,6 +73,8 @@ The following methods are used by REST framework to instantiate the various plug
### .get_content_negotiator(self)
### .get_exception_handler(self)
## API policy implementation methods
The following methods are called before dispatching to the handler method.

View File

@ -73,7 +73,7 @@ The initial aim is to provide a single full-time position on REST framework.
<ul class="premium-promo promo">
<li><a href="http://jobs.rover.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://getsentry.com/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</a></li>
<li><a href="https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
<li><a href="https://getstream.io/try-the-api/?utm_source=drf&utm_medium=banner&utm_campaign=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/stream-130.png)">Stream</a></li>
<li><a href="http://www.machinalis.com/#services" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>

View File

@ -147,10 +147,10 @@ When using a serializer with a `HyperlinkedRelatedField` or `HyperlinkedIdentity
From version 2.2 onwards, serializers with hyperlinked relationships *always* require a `'request'` key to be supplied in the context dictionary. The implicit behavior will continue to function, but its use will raise a `PendingDeprecationWarning`.
[xordoquy]: https://github.com/xordoquy
[django-python-3]: https://docs.djangoproject.com/en/dev/faq/install/#can-i-use-django-with-python-3
[porting-python-3]: https://docs.djangoproject.com/en/dev/topics/python3/
[python-compat]: https://docs.djangoproject.com/en/dev/releases/1.5/#python-compatibility
[django-deprecation-policy]: https://docs.djangoproject.com/en/dev/internals/release-process/#internal-release-deprecation-policy
[django-python-3]: https://docs.djangoproject.com/en/stable/faq/install/#can-i-use-django-with-python-3
[porting-python-3]: https://docs.djangoproject.com/en/stable/topics/python3/
[python-compat]: https://docs.djangoproject.com/en/stable/releases/1.5/#python-compatibility
[django-deprecation-policy]: https://docs.djangoproject.com/en/stable/internals/release-process/#internal-release-deprecation-policy
[credits]: http://www.django-rest-framework.org/topics/credits
[mailing-list]: https://groups.google.com/forum/?fromgroups#!forum/django-rest-framework
[django-rest-framework-docs]: https://github.com/marcgibbons/django-rest-framework-docs

View File

@ -162,7 +162,7 @@ The next planned release will be 3.0, featuring an improved and simplified seria
Once again, many thanks to all the generous [backers and sponsors][kickstarter-sponsors] who've helped make this possible!
[lts-releases]: https://docs.djangoproject.com/en/dev/internals/release-process/#long-term-support-lts-releases
[lts-releases]: https://docs.djangoproject.com/en/stable/internals/release-process/#long-term-support-lts-releases
[2-4-release-notes]: release-notes#240
[view-name-and-description-settings]: ../api-guide/settings#view-names-and-descriptions
[client-ip-identification]: ../api-guide/throttling#how-clients-are-identified

View File

@ -870,7 +870,7 @@ The `COMPACT_JSON` setting has been added, and can be used to revert this behavi
#### File fields as URLs
The `FileField` and `ImageField` classes are now represented as URLs by default. You should ensure you set Django's [standard `MEDIA_URL` setting](https://docs.djangoproject.com/en/dev/ref/settings/#std:setting-MEDIA_URL) appropriately, and ensure your application [serves the uploaded files](https://docs.djangoproject.com/en/dev/howto/static-files/#serving-uploaded-files-in-development).
The `FileField` and `ImageField` classes are now represented as URLs by default. You should ensure you set Django's [standard `MEDIA_URL` setting](https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-MEDIA_URL) appropriately, and ensure your application [serves the uploaded files](https://docs.djangoproject.com/en/stable/howto/static-files/#serving-uploaded-files-in-development).
You can revert this behavior, and display filenames in the representation by using the `UPLOADED_FILES_USE_URL` settings key:
@ -962,4 +962,4 @@ You can follow development on the GitHub site, where we use [milestones to indic
[kickstarter]: http://kickstarter.com/projects/tomchristie/django-rest-framework-3
[sponsors]: http://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors
[mixins.py]: https://github.com/tomchristie/django-rest-framework/blob/master/rest_framework/mixins.py
[django-localization]: https://docs.djangoproject.com/en/dev/topics/i18n/translation/#localization-how-to-create-language-files
[django-localization]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#localization-how-to-create-language-files

View File

@ -35,7 +35,7 @@ The best way to deal with CORS in REST framework is to add the required response
[cite]: http://www.codinghorror.com/blog/2008/10/preventing-csrf-and-xsrf-attacks.html
[csrf]: https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
[csrf-ajax]: https://docs.djangoproject.com/en/dev/ref/csrf/#ajax
[csrf-ajax]: https://docs.djangoproject.com/en/stable/ref/csrf/#ajax
[cors]: http://www.w3.org/TR/cors/
[ottoyiu]: https://github.com/ottoyiu/
[django-cors-headers]: https://github.com/ottoyiu/django-cors-headers/

View File

@ -38,6 +38,34 @@ Both this package and DRF docs are fully documented, well supported, and come hi
---
### DRF AutoDocs
Oleksander Mashianovs' [DRF Auto Docs][drfautodocs-repo] automated api renderer.
Collects almost all the code you written into documentation effortlessly.
Supports:
* functional view docs
* tree-like structure
* Docstrings:
* markdown
* preserve space & newlines
* formatting with nice syntax
* Fields:
* choices rendering
* help_text (to specify SerializerMethodField output, etc)
* smart read_only/required rendering
* Endpoint properties:
* filter_backends
* authentication_classes
* permission_classes
* extra url params(GET params)
![whole structure](http://joxi.ru/52aBGNI4k3oyA0.jpg)
---
#### Apiary
There are various other online tools and services for providing API documentation. One notable service is [Apiary][apiary]. With Apiary, you describe your API using a simple markdown-like syntax. The generated documentation includes API interaction, a mock server for testing & prototyping, and various other tools.
@ -109,6 +137,7 @@ To implement a hypermedia API you'll need to decide on an appropriate media type
[drfdocs-repo]: https://github.com/ekonstantinidis/django-rest-framework-docs
[drfdocs-website]: http://www.drfdocs.com/
[drfdocs-demo]: http://demo.drfdocs.com/
[drfautodocs-repo]: https://github.com/iMakedonsky/drf-autodocs
[django-rest-swagger]: https://github.com/marcgibbons/django-rest-swagger
[swagger]: https://developers.helloreverb.com/swagger/
[rest-framework-docs]: https://github.com/marcgibbons/django-rest-framework-docs

View File

@ -598,7 +598,7 @@ For older release notes, [please see the version 2.x documentation][old-release-
[cite]: http://www.catb.org/~esr/writings/cathedral-bazaar/cathedral-bazaar/ar01s04.html
[deprecation-policy]: #deprecation-policy
[django-deprecation-policy]: https://docs.djangoproject.com/en/dev/internals/release-process/#internal-release-deprecation-policy
[django-deprecation-policy]: https://docs.djangoproject.com/en/stable/internals/release-process/#internal-release-deprecation-policy
[defusedxml-announce]: http://blog.python.org/2013/02/announcing-defusedxml-fixes-for-xml.html
[743]: https://github.com/tomchristie/django-rest-framework/pull/743
[staticfiles14]: https://docs.djangoproject.com/en/1.4/howto/static-files/#with-a-template-tag

View File

@ -189,6 +189,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [djangorestframework-httpsignature][djangorestframework-httpsignature] - Provides an easy to use HTTP Signature Authentication mechanism.
* [djoser][djoser] - Provides a set of views to handle basic actions such as registration, login, logout, password reset and account activation.
* [django-rest-auth][django-rest-auth] - Provides a set of REST API endpoints for registration, authentication (including social media authentication), password reset, retrieve and update user details, etc.
* [drf-oidc-auth][drf-oidc-auth] - Implements OpenID Connect token authentication for DRF.
### Permissions
@ -203,7 +204,9 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [djangorestframework-gis][djangorestframework-gis] - Geographic add-ons
* [djangorestframework-hstore][djangorestframework-hstore] - Serializer class to support django-hstore DictionaryField model field and its schema-mode feature.
* [djangorestframework-jsonapi][djangorestframework-jsonapi] - Provides a parser, renderer, serializers, and other tools to help build an API that is compliant with the jsonapi.org spec.
* [html-json-forms][html-json-forms]: Provides an algorithm and serializer to process HTML JSON Form submissions per the (inactive) spec.
* [html-json-forms][html-json-forms] - Provides an algorithm and serializer to process HTML JSON Form submissions per the (inactive) spec.
* [django-rest-framework-serializer-extensions][drf-serializer-extensions] -
Enables black/whitelisting fields, and conditionally expanding child serializers on a per-view/request basis.
### Serializer fields
@ -365,3 +368,5 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
[medium-django-rest-framework]: https://medium.com/django-rest-framework
[django-rest-framework-course]: https://teamtreehouse.com/library/django-rest-framework
[drf_tweaks]: https://github.com/ArabellaTech/drf_tweaks
[drf-oidc-auth]: https://github.com/ByteInternet/drf-oidc-auth
[drf-serializer-extensions]: https://github.com/evenicoulddoit/django-rest-framework-serializer-extensions

View File

@ -118,7 +118,9 @@ We'd also like to set a few global settings. We'd like to turn on pagination, a
)
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': ('rest_framework.permissions.IsAdminUser',),
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAdminUser',
],
'PAGE_SIZE': 10
}

View File

@ -1652,7 +1652,7 @@ class ReadOnlyField(Field):
A read-only field that simply returns the field value.
If the field is a method with no parameters, the method will be called
and it's return value used as the representation.
and its return value used as the representation.
For example, the following would call `get_expiry_date()` on the object:

View File

@ -453,7 +453,7 @@ class SchemaGenerator(object):
current_section, seperator, lead = line.partition(':')
sections[current_section] = lead.strip()
else:
sections[current_section] += line + '\n'
sections[current_section] += '\n' + line
header = getattr(view, 'action', method.lower())
if header in sections:

View File

@ -305,7 +305,11 @@ class SerializerMetaclass(type):
# in order to maintain the correct order of fields.
for base in reversed(bases):
if hasattr(base, '_declared_fields'):
fields = list(base._declared_fields.items()) + fields
fields = [
(field_name, obj) for field_name, obj
in base._declared_fields.items()
if field_name not in attrs
] + fields
return OrderedDict(fields)

View File

@ -28,7 +28,9 @@ def get_breadcrumbs(url, request=None):
# Don't list the same view twice in a row.
# Probably an optional trailing slash.
if not seen or seen[-1] != view:
name = cls().get_view_name()
c = cls()
c.suffix = getattr(view, 'suffix', None)
name = c.get_view_name()
insert_url = preserve_builtin_query_params(prefix + url, request)
breadcrumbs_list.insert(0, (name, insert_url))
seen.append(view)

View File

@ -286,6 +286,12 @@ class APIView(View):
self._negotiator = self.content_negotiation_class()
return self._negotiator
def get_exception_handler(self):
"""
Returns the exception handler that this view uses.
"""
return api_settings.EXCEPTION_HANDLER
# API policy implementation methods
def perform_content_negotiation(self, request, force=False):
@ -428,7 +434,7 @@ class APIView(View):
else:
exc.status_code = status.HTTP_403_FORBIDDEN
exception_handler = self.settings.EXCEPTION_HANDLER
exception_handler = self.get_exception_handler()
context = self.get_exception_handler_context()
response = exception_handler(exc, context)

View File

@ -26,16 +26,19 @@ class DropdownWithAuthTests(TestCase):
def test_name_shown_when_logged_in(self):
self.client.login(username=self.username, password=self.password)
response = self.client.get('/')
self.assertContains(response, 'john')
content = response.content.decode('utf8')
assert 'john' in content
def test_logout_shown_when_logged_in(self):
self.client.login(username=self.username, password=self.password)
response = self.client.get('/')
self.assertContains(response, '>Log out<')
content = response.content.decode('utf8')
assert '>Log out<' in content
def test_login_shown_when_logged_out(self):
response = self.client.get('/')
self.assertContains(response, '>Log in<')
content = response.content.decode('utf8')
assert '>Log in<' in content
@override_settings(ROOT_URLCONF='tests.browsable_api.no_auth_urls')
@ -58,13 +61,16 @@ class NoDropdownWithoutAuthTests(TestCase):
def test_name_shown_when_logged_in(self):
self.client.login(username=self.username, password=self.password)
response = self.client.get('/')
self.assertContains(response, 'john')
content = response.content.decode('utf8')
assert 'john' in content
def test_dropdown_not_shown_when_logged_in(self):
self.client.login(username=self.username, password=self.password)
response = self.client.get('/')
self.assertNotContains(response, '<li class="dropdown">')
content = response.content.decode('utf8')
assert '<li class="dropdown">' not in content
def test_dropdown_not_shown_when_logged_out(self):
response = self.client.get('/')
self.assertNotContains(response, '<li class="dropdown">')
content = response.content.decode('utf8')
assert '<li class="dropdown">' not in content

View File

@ -35,8 +35,8 @@ class DropdownWithAuthTests(TestCase):
@override_settings(ROOT_URLCONF='tests.browsable_api.test_browsable_nested_api')
def test_login(self):
response = self.client.get('/api/')
self.assertEqual(200, response.status_code)
assert 200 == response.status_code
content = response.content.decode('utf-8')
self.assertIn('form action="/api/"', content)
self.assertIn('input name="nested.one"', content)
self.assertIn('input name="nested.two"', content)
assert 'form action="/api/"' in content
assert 'input name="nested.one"' in content
assert 'input name="nested.two"' in content

View File

@ -50,5 +50,5 @@ class TestManyPostView(TestCase):
request = factory.post('/', data, format='json')
with self.assertNumQueries(1):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 3)
assert response.status_code == status.HTTP_200_OK
assert len(response.data) == 3

View File

@ -67,8 +67,8 @@ class DBTransactionTests(TestCase):
with self.assertNumQueries(1):
response = self.view(request)
self.assertFalse(transaction.get_rollback())
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert not transaction.get_rollback()
assert response.status_code == status.HTTP_200_OK
assert BasicModel.objects.count() == 1
@ -98,7 +98,7 @@ class DBTransactionErrorTests(TestCase):
# 3 - release savepoint
with transaction.atomic():
self.assertRaises(Exception, self.view, request)
self.assertFalse(transaction.get_rollback())
assert not transaction.get_rollback()
assert BasicModel.objects.count() == 1
@ -128,9 +128,8 @@ class DBTransactionAPIExceptionTests(TestCase):
# 4 - release savepoint (django>=1.8 only)
with transaction.atomic():
response = self.view(request)
self.assertTrue(transaction.get_rollback())
self.assertEqual(response.status_code,
status.HTTP_500_INTERNAL_SERVER_ERROR)
assert transaction.get_rollback()
assert response.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
assert BasicModel.objects.count() == 0
@ -151,5 +150,4 @@ class NonAtomicDBTransactionAPIExceptionTests(TransactionTestCase):
# without checking connection.in_atomic_block view raises 500
# due attempt to rollback without transaction
self.assertEqual(response.status_code,
status.HTTP_404_NOT_FOUND)
assert response.status_code == status.HTTP_404_NOT_FOUND

View File

@ -106,7 +106,7 @@ class BasicAuthTests(TestCase):
{'example': 'example'},
HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_post_json_passing_basic_auth(self):
"""Ensure POSTing form over basic auth with correct credentials passes and does not require CSRF"""
@ -121,7 +121,7 @@ class BasicAuthTests(TestCase):
format='json',
HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_regression_handle_bad_base64_basic_auth_header(self):
"""Ensure POSTing JSON over basic auth with incorrectly padded Base64 string is handled correctly"""
@ -134,12 +134,12 @@ class BasicAuthTests(TestCase):
format='json',
HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_post_form_failing_basic_auth(self):
"""Ensure POSTing form over basic auth without correct credentials fails"""
response = self.csrf_client.post('/basic/', {'example': 'example'})
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_post_json_failing_basic_auth(self):
"""Ensure POSTing json over basic auth without correct credentials fails"""
@ -148,8 +148,8 @@ class BasicAuthTests(TestCase):
{'example': 'example'},
format='json'
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
self.assertEqual(response['WWW-Authenticate'], 'Basic realm="api"')
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert response['WWW-Authenticate'] == 'Basic realm="api"'
@override_settings(ROOT_URLCONF='tests.test_authentication')
@ -175,9 +175,8 @@ class SessionAuthTests(TestCase):
cf. [#1810](https://github.com/tomchristie/django-rest-framework/pull/1810)
"""
response = self.csrf_client.get('/auth/login/')
self.assertContains(
response, '<label for="id_username">Username:</label>'
)
content = response.content.decode('utf8')
assert '<label for="id_username">Username:</label>' in content
def test_post_form_session_auth_failing_csrf(self):
"""
@ -185,7 +184,7 @@ class SessionAuthTests(TestCase):
"""
self.csrf_client.login(username=self.username, password=self.password)
response = self.csrf_client.post('/session/', {'example': 'example'})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
assert response.status_code == status.HTTP_403_FORBIDDEN
def test_post_form_session_auth_passing(self):
"""
@ -198,7 +197,7 @@ class SessionAuthTests(TestCase):
response = self.non_csrf_client.post(
'/session/', {'example': 'example'}
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_put_form_session_auth_passing(self):
"""
@ -211,14 +210,14 @@ class SessionAuthTests(TestCase):
response = self.non_csrf_client.put(
'/session/', {'example': 'example'}
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_post_form_session_auth_failing(self):
"""
Ensure POSTing form over session authentication without logged in user fails.
"""
response = self.csrf_client.post('/session/', {'example': 'example'})
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
assert response.status_code == status.HTTP_403_FORBIDDEN
class BaseTokenAuthTests(object):
@ -248,7 +247,7 @@ class BaseTokenAuthTests(object):
response = self.csrf_client.post(
self.path, {'example': 'example'}, HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_fail_post_form_passing_nonexistent_token_auth(self):
# use a nonexistent token key
@ -256,7 +255,7 @@ class BaseTokenAuthTests(object):
response = self.csrf_client.post(
self.path, {'example': 'example'}, HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_fail_post_form_passing_invalid_token_auth(self):
# add an 'invalid' unicode character
@ -264,7 +263,7 @@ class BaseTokenAuthTests(object):
response = self.csrf_client.post(
self.path, {'example': 'example'}, HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_post_json_passing_token_auth(self):
"""
@ -276,7 +275,7 @@ class BaseTokenAuthTests(object):
self.path, {'example': 'example'},
format='json', HTTP_AUTHORIZATION=auth
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_post_json_makes_one_db_query(self):
"""
@ -298,7 +297,7 @@ class BaseTokenAuthTests(object):
Ensure POSTing form over token auth without correct credentials fails
"""
response = self.csrf_client.post(self.path, {'example': 'example'})
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_post_json_failing_token_auth(self):
"""
@ -307,7 +306,7 @@ class BaseTokenAuthTests(object):
response = self.csrf_client.post(
self.path, {'example': 'example'}, format='json'
)
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
@override_settings(ROOT_URLCONF='tests.test_authentication')
@ -319,13 +318,13 @@ class TokenAuthTests(BaseTokenAuthTests, TestCase):
"""Ensure creating a token with no key will auto-assign a key"""
self.token.delete()
token = self.model.objects.create(user=self.user)
self.assertTrue(bool(token.key))
assert bool(token.key)
def test_generate_key_returns_string(self):
"""Ensure generate_key returns a string"""
token = self.model()
key = token.generate_key()
self.assertTrue(isinstance(key, six.string_types))
assert isinstance(key, six.string_types)
def test_token_login_json(self):
"""Ensure token login view using JSON POST works."""
@ -335,8 +334,8 @@ class TokenAuthTests(BaseTokenAuthTests, TestCase):
{'username': self.username, 'password': self.password},
format='json'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['token'], self.key)
assert response.status_code == status.HTTP_200_OK
assert response.data['token'] == self.key
def test_token_login_json_bad_creds(self):
"""
@ -349,22 +348,24 @@ class TokenAuthTests(BaseTokenAuthTests, TestCase):
{'username': self.username, 'password': "badpass"},
format='json'
)
self.assertEqual(response.status_code, 400)
assert response.status_code == 400
def test_token_login_json_missing_fields(self):
"""Ensure token login view using JSON POST fails if missing fields."""
client = APIClient(enforce_csrf_checks=True)
response = client.post('/auth-token/',
{'username': self.username}, format='json')
self.assertEqual(response.status_code, 400)
assert response.status_code == 400
def test_token_login_form(self):
"""Ensure token login view using form POST works."""
client = APIClient(enforce_csrf_checks=True)
response = client.post('/auth-token/',
{'username': self.username, 'password': self.password})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['token'], self.key)
response = client.post(
'/auth-token/',
{'username': self.username, 'password': self.password}
)
assert response.status_code == status.HTTP_200_OK
assert response.data['token'] == self.key
@override_settings(ROOT_URLCONF='tests.test_authentication')
@ -397,8 +398,8 @@ class IncorrectCredentialsTests(TestCase):
permission_classes=()
)
response = view(request)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.data, {'detail': 'Bad credentials'})
assert response.status_code == status.HTTP_403_FORBIDDEN
assert response.data == {'detail': 'Bad credentials'}
class FailingAuthAccessedInRenderer(TestCase):
@ -435,7 +436,7 @@ class FailingAuthAccessedInRenderer(TestCase):
request = factory.get('/')
response = self.view(request)
content = response.render().content
self.assertEqual(content, b'not authenticated')
assert content == b'not authenticated'
class NoAuthenticationClassesTests(TestCase):
@ -458,6 +459,5 @@ class NoAuthenticationClassesTests(TestCase):
permission_classes=(DummyPermission,),
)
response = view(request)
self.assertEqual(response.status_code,
status.HTTP_403_FORBIDDEN)
self.assertEqual(response.data, {'detail': 'Dummy permission message'})
assert response.status_code == status.HTTP_403_FORBIDDEN
assert response.data == {'detail': 'Dummy permission message'}

View File

@ -56,11 +56,11 @@ class DecoratorTestCase(TestCase):
request = self.factory.get('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
request = self.factory.post('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def test_calling_put_method(self):
@ -70,11 +70,11 @@ class DecoratorTestCase(TestCase):
request = self.factory.put('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
request = self.factory.post('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def test_calling_patch_method(self):
@ -84,11 +84,11 @@ class DecoratorTestCase(TestCase):
request = self.factory.patch('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
request = self.factory.post('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
def test_renderer_classes(self):
@ -99,16 +99,15 @@ class DecoratorTestCase(TestCase):
request = self.factory.get('/')
response = view(request)
self.assertTrue(isinstance(response.accepted_renderer, JSONRenderer))
assert isinstance(response.accepted_renderer, JSONRenderer)
def test_parser_classes(self):
@api_view(['GET'])
@parser_classes([JSONParser])
def view(request):
self.assertEqual(len(request.parsers), 1)
self.assertTrue(isinstance(request.parsers[0],
JSONParser))
assert len(request.parsers) == 1
assert isinstance(request.parsers[0], JSONParser)
return Response({})
request = self.factory.get('/')
@ -119,9 +118,8 @@ class DecoratorTestCase(TestCase):
@api_view(['GET'])
@authentication_classes([BasicAuthentication])
def view(request):
self.assertEqual(len(request.authenticators), 1)
self.assertTrue(isinstance(request.authenticators[0],
BasicAuthentication))
assert len(request.authenticators) == 1
assert isinstance(request.authenticators[0], BasicAuthentication)
return Response({})
request = self.factory.get('/')
@ -136,7 +134,7 @@ class DecoratorTestCase(TestCase):
request = self.factory.get('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
assert response.status_code == status.HTTP_403_FORBIDDEN
def test_throttle_classes(self):
class OncePerDayUserThrottle(UserRateThrottle):
@ -149,7 +147,7 @@ class DecoratorTestCase(TestCase):
request = self.factory.get('/')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
response = view(request)
self.assertEqual(response.status_code, status.HTTP_429_TOO_MANY_REQUESTS)
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS

View File

@ -60,7 +60,7 @@ class TestViewNamesAndDescriptions(TestCase):
"""
class MockView(APIView):
pass
self.assertEqual(MockView().get_view_name(), 'Mock')
assert MockView().get_view_name() == 'Mock'
def test_view_description_uses_docstring(self):
"""Ensure view descriptions are based on the docstring."""
@ -80,7 +80,7 @@ class TestViewNamesAndDescriptions(TestCase):
# hash style header #"""
self.assertEqual(MockView().get_view_description(), DESCRIPTION)
assert MockView().get_view_description() == DESCRIPTION
def test_view_description_can_be_empty(self):
"""
@ -89,7 +89,7 @@ class TestViewNamesAndDescriptions(TestCase):
"""
class MockView(APIView):
pass
self.assertEqual(MockView().get_view_description(), '')
assert MockView().get_view_description() == ''
def test_view_description_can_be_promise(self):
"""
@ -111,7 +111,7 @@ class TestViewNamesAndDescriptions(TestCase):
class MockView(APIView):
__doc__ = MockLazyStr("a gettext string")
self.assertEqual(MockView().get_view_description(), 'a gettext string')
assert MockView().get_view_description() == 'a gettext string'
def test_markdown(self):
"""
@ -120,7 +120,7 @@ class TestViewNamesAndDescriptions(TestCase):
if apply_markdown:
gte_21_match = apply_markdown(DESCRIPTION) == MARKED_DOWN_gte_21
lt_21_match = apply_markdown(DESCRIPTION) == MARKED_DOWN_lt_21
self.assertTrue(gte_21_match or lt_21_match)
assert gte_21_match or lt_21_match
def test_dedent_tabs():

View File

@ -20,21 +20,21 @@ class JSONEncoderTests(TestCase):
Tests encoding a decimal
"""
d = Decimal(3.14)
self.assertEqual(d, float(d))
assert d == float(d)
def test_encode_datetime(self):
"""
Tests encoding a datetime object
"""
current_time = datetime.now()
self.assertEqual(self.encoder.default(current_time), current_time.isoformat())
assert self.encoder.default(current_time) == current_time.isoformat()
def test_encode_time(self):
"""
Tests encoding a timezone
"""
current_time = datetime.now().time()
self.assertEqual(self.encoder.default(current_time), current_time.isoformat()[:12])
assert self.encoder.default(current_time) == current_time.isoformat()[:12]
def test_encode_time_tz(self):
"""
@ -64,18 +64,18 @@ class JSONEncoderTests(TestCase):
Tests encoding a date object
"""
current_date = date.today()
self.assertEqual(self.encoder.default(current_date), current_date.isoformat())
assert self.encoder.default(current_date) == current_date.isoformat()
def test_encode_timedelta(self):
"""
Tests encoding a timedelta object
"""
delta = timedelta(hours=1)
self.assertEqual(self.encoder.default(delta), str(delta.total_seconds()))
assert self.encoder.default(delta) == str(delta.total_seconds())
def test_encode_uuid(self):
"""
Tests encoding a UUID object
"""
unique_id = uuid4()
self.assertEqual(self.encoder.default(unique_id), str(unique_id))
assert self.encoder.default(unique_id) == str(unique_id)

View File

@ -16,28 +16,22 @@ class ExceptionTestCase(TestCase):
example = "string"
lazy_example = _(example)
self.assertEqual(
_get_error_details(lazy_example),
example
)
assert _get_error_details(lazy_example) == example
assert isinstance(
_get_error_details(lazy_example),
ErrorDetail
)
self.assertEqual(
_get_error_details({'nested': lazy_example})['nested'],
example
)
assert _get_error_details({'nested': lazy_example})['nested'] == example
assert isinstance(
_get_error_details({'nested': lazy_example})['nested'],
ErrorDetail
)
self.assertEqual(
_get_error_details([[lazy_example]])[0][0],
example
)
assert _get_error_details([[lazy_example]])[0][0] == example
assert isinstance(
_get_error_details([[lazy_example]])[0][0],
ErrorDetail

View File

@ -1014,16 +1014,16 @@ class TestLocalizedDecimalField(TestCase):
@override_settings(USE_L10N=True, LANGUAGE_CODE='pl')
def test_to_internal_value(self):
field = serializers.DecimalField(max_digits=2, decimal_places=1, localize=True)
self.assertEqual(field.to_internal_value('1,1'), Decimal('1.1'))
assert field.to_internal_value('1,1') == Decimal('1.1')
@override_settings(USE_L10N=True, LANGUAGE_CODE='pl')
def test_to_representation(self):
field = serializers.DecimalField(max_digits=2, decimal_places=1, localize=True)
self.assertEqual(field.to_representation(Decimal('1.1')), '1,1')
assert field.to_representation(Decimal('1.1')) == '1,1'
def test_localize_forces_coerce_to_string(self):
field = serializers.DecimalField(max_digits=2, decimal_places=1, coerce_to_string=False, localize=True)
self.assertTrue(isinstance(field.to_representation(Decimal('1.1')), six.string_types))
assert isinstance(field.to_representation(Decimal('1.1')), six.string_types)
class TestQuantizedValueForDecimal(TestCase):
@ -1031,19 +1031,19 @@ class TestQuantizedValueForDecimal(TestCase):
field = serializers.DecimalField(max_digits=4, decimal_places=2)
value = field.to_internal_value(12).as_tuple()
expected_digit_tuple = (0, (1, 2, 0, 0), -2)
self.assertEqual(value, expected_digit_tuple)
assert value == expected_digit_tuple
def test_string_quantized_value_for_decimal(self):
field = serializers.DecimalField(max_digits=4, decimal_places=2)
value = field.to_internal_value('12').as_tuple()
expected_digit_tuple = (0, (1, 2, 0, 0), -2)
self.assertEqual(value, expected_digit_tuple)
assert value == expected_digit_tuple
def test_part_precision_string_quantized_value_for_decimal(self):
field = serializers.DecimalField(max_digits=4, decimal_places=2)
value = field.to_internal_value('12.0').as_tuple()
expected_digit_tuple = (0, (1, 2, 0, 0), -2)
self.assertEqual(value, expected_digit_tuple)
assert value == expected_digit_tuple
class TestNoDecimalPlaces(FieldValues):

View File

@ -155,8 +155,8 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
request = factory.get('/')
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data)
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data
self.assertTrue(issubclass(w[-1].category, PendingDeprecationWarning))
self.assertIn("'rest_framework.filters.DjangoFilterBackend' is pending deprecation.", str(w[-1].message))
@ -175,9 +175,9 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
request = factory.get('/')
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data)
self.assertEqual(len(w), 0)
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data
assert len(w) == 0
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_get_filtered_fields_root_view(self):
@ -189,24 +189,24 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
# Basic test with no filter.
request = factory.get('/')
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data)
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data
# Tests that the decimal filter works.
search_decimal = Decimal('2.25')
request = factory.get('/', {'decimal': '%s' % search_decimal})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if Decimal(f['decimal']) == search_decimal]
self.assertEqual(response.data, expected_data)
assert response.data == expected_data
# Tests that the date filter works.
search_date = datetime.date(2012, 9, 22)
request = factory.get('/', {'date': '%s' % search_date}) # search_date str: '2012-09-22'
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if parse_date(f['date']) == search_date]
self.assertEqual(response.data, expected_data)
assert response.data == expected_data
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_filter_with_queryset(self):
@ -219,9 +219,9 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
search_decimal = Decimal('2.25')
request = factory.get('/', {'decimal': '%s' % search_decimal})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if Decimal(f['decimal']) == search_decimal]
self.assertEqual(response.data, expected_data)
assert response.data == expected_data
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_filter_with_get_queryset_only(self):
@ -245,32 +245,32 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
# Basic test with no filter.
request = factory.get('/')
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data)
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data
# Tests that the decimal filter set with 'lt' in the filter class works.
search_decimal = Decimal('4.25')
request = factory.get('/', {'decimal': '%s' % search_decimal})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if Decimal(f['decimal']) < search_decimal]
self.assertEqual(response.data, expected_data)
assert response.data == expected_data
# Tests that the date filter set with 'gt' in the filter class works.
search_date = datetime.date(2012, 10, 2)
request = factory.get('/', {'date': '%s' % search_date}) # search_date str: '2012-10-02'
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if parse_date(f['date']) > search_date]
self.assertEqual(response.data, expected_data)
assert response.data == expected_data
# Tests that the text filter set with 'icontains' in the filter class works.
search_text = 'ff'
request = factory.get('/', {'text': '%s' % search_text})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if search_text in f['text'].lower()]
self.assertEqual(response.data, expected_data)
assert response.data == expected_data
# Tests that multiple filters works.
search_decimal = Decimal('5.25')
@ -280,10 +280,10 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
'date': '%s' % (search_date,)
})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
expected_data = [f for f in self.data if parse_date(f['date']) > search_date and
Decimal(f['decimal']) < search_decimal]
self.assertEqual(response.data, expected_data)
assert response.data == expected_data
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_incorrectly_configured_filter(self):
@ -304,8 +304,8 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
request = factory.get('/?text=aaa')
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
assert response.status_code == status.HTTP_200_OK
assert len(response.data) == 1
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_base_model_filter_with_proxy(self):
@ -316,8 +316,8 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
request = factory.get('/?text=aaa')
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
assert response.status_code == status.HTTP_200_OK
assert len(response.data) == 1
@unittest.skipUnless(django_filters, 'django-filter not installed')
def test_unknown_filter(self):
@ -329,7 +329,7 @@ class IntegrationTestFiltering(CommonFilteringTestCase):
search_integer = 10
request = factory.get('/', {'integer': '%s' % search_integer})
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
@override_settings(ROOT_URLCONF='tests.test_filters')
@ -351,8 +351,8 @@ class IntegrationTestDetailFiltering(CommonFilteringTestCase):
# Basic test with no filter.
response = self.client.get(self._get_url(item))
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, data)
assert response.status_code == status.HTTP_200_OK
assert response.data == data
# Tests that the decimal filter set that should fail.
search_decimal = Decimal('4.25')
@ -360,7 +360,7 @@ class IntegrationTestDetailFiltering(CommonFilteringTestCase):
response = self.client.get(
'{url}'.format(url=self._get_url(high_item)),
{'decimal': '{param}'.format(param=search_decimal)})
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
assert response.status_code == status.HTTP_404_NOT_FOUND
# Tests that the decimal filter set that should succeed.
search_decimal = Decimal('4.25')
@ -369,8 +369,8 @@ class IntegrationTestDetailFiltering(CommonFilteringTestCase):
response = self.client.get(
'{url}'.format(url=self._get_url(low_item)),
{'decimal': '{param}'.format(param=search_decimal)})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, low_item_data)
assert response.status_code == status.HTTP_200_OK
assert response.data == low_item_data
# Tests that multiple filters works.
search_decimal = Decimal('5.25')
@ -382,8 +382,8 @@ class IntegrationTestDetailFiltering(CommonFilteringTestCase):
'decimal': '{decimal}'.format(decimal=search_decimal),
'date': '{date}'.format(date=search_date)
})
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, valid_item_data)
assert response.status_code == status.HTTP_200_OK
assert response.data == valid_item_data
class SearchFilterModel(models.Model):
@ -424,13 +424,10 @@ class SearchFilterTests(TestCase):
view = SearchListView.as_view()
request = factory.get('/', {'search': 'b'})
response = view(request)
self.assertEqual(
response.data,
[
{'id': 1, 'title': 'z', 'text': 'abc'},
{'id': 2, 'title': 'zz', 'text': 'bcd'}
]
)
assert response.data == [
{'id': 1, 'title': 'z', 'text': 'abc'},
{'id': 2, 'title': 'zz', 'text': 'bcd'}
]
def test_exact_search(self):
class SearchListView(generics.ListAPIView):
@ -442,12 +439,9 @@ class SearchFilterTests(TestCase):
view = SearchListView.as_view()
request = factory.get('/', {'search': 'zzz'})
response = view(request)
self.assertEqual(
response.data,
[
{'id': 3, 'title': 'zzz', 'text': 'cde'}
]
)
assert response.data == [
{'id': 3, 'title': 'zzz', 'text': 'cde'}
]
def test_startswith_search(self):
class SearchListView(generics.ListAPIView):
@ -459,12 +453,9 @@ class SearchFilterTests(TestCase):
view = SearchListView.as_view()
request = factory.get('/', {'search': 'b'})
response = view(request)
self.assertEqual(
response.data,
[
{'id': 2, 'title': 'zz', 'text': 'bcd'}
]
)
assert response.data == [
{'id': 2, 'title': 'zz', 'text': 'bcd'}
]
def test_regexp_search(self):
class SearchListView(generics.ListAPIView):
@ -476,12 +467,9 @@ class SearchFilterTests(TestCase):
view = SearchListView.as_view()
request = factory.get('/', {'search': 'z{2} ^b'})
response = view(request)
self.assertEqual(
response.data,
[
{'id': 2, 'title': 'zz', 'text': 'bcd'}
]
)
assert response.data == [
{'id': 2, 'title': 'zz', 'text': 'bcd'}
]
def test_search_with_nonstandard_search_param(self):
with override_settings(REST_FRAMEWORK={'SEARCH_PARAM': 'query'}):
@ -496,13 +484,10 @@ class SearchFilterTests(TestCase):
view = SearchListView.as_view()
request = factory.get('/', {'query': 'b'})
response = view(request)
self.assertEqual(
response.data,
[
{'id': 1, 'title': 'z', 'text': 'abc'},
{'id': 2, 'title': 'zz', 'text': 'bcd'}
]
)
assert response.data == [
{'id': 1, 'title': 'z', 'text': 'abc'},
{'id': 2, 'title': 'zz', 'text': 'bcd'}
]
reload_module(filters)
@ -528,15 +513,13 @@ class SearchFilterFkTests(TestCase):
filter_ = filters.SearchFilter()
prefixes = [''] + list(filter_.lookup_prefixes)
for prefix in prefixes:
self.assertFalse(
filter_.must_call_distinct(
SearchFilterModelFk._meta, ["%stitle" % prefix]
)
assert not filter_.must_call_distinct(
SearchFilterModelFk._meta,
["%stitle" % prefix]
)
self.assertFalse(
filter_.must_call_distinct(
SearchFilterModelFk._meta, ["%stitle" % prefix, "%sattribute__label" % prefix]
)
assert not filter_.must_call_distinct(
SearchFilterModelFk._meta,
["%stitle" % prefix, "%sattribute__label" % prefix]
)
def test_must_call_distinct_restores_meta_for_each_field(self):
@ -545,10 +528,9 @@ class SearchFilterFkTests(TestCase):
filter_ = filters.SearchFilter()
prefixes = [''] + list(filter_.lookup_prefixes)
for prefix in prefixes:
self.assertFalse(
filter_.must_call_distinct(
SearchFilterModelFk._meta, ["%sattribute__label" % prefix, "%stitle" % prefix]
)
assert not filter_.must_call_distinct(
SearchFilterModelFk._meta,
["%sattribute__label" % prefix, "%stitle" % prefix]
)
@ -596,21 +578,20 @@ class SearchFilterM2MTests(TestCase):
view = SearchListView.as_view()
request = factory.get('/', {'search': 'zz'})
response = view(request)
self.assertEqual(len(response.data), 1)
assert len(response.data) == 1
def test_must_call_distinct(self):
filter_ = filters.SearchFilter()
prefixes = [''] + list(filter_.lookup_prefixes)
for prefix in prefixes:
self.assertFalse(
filter_.must_call_distinct(
SearchFilterModelM2M._meta, ["%stitle" % prefix]
)
assert not filter_.must_call_distinct(
SearchFilterModelM2M._meta,
["%stitle" % prefix]
)
self.assertTrue(
filter_.must_call_distinct(
SearchFilterModelM2M._meta, ["%stitle" % prefix, "%sattributes__label" % prefix]
)
assert filter_.must_call_distinct(
SearchFilterModelM2M._meta,
["%stitle" % prefix, "%sattributes__label" % prefix]
)
@ -672,14 +653,11 @@ class DjangoFilterOrderingTests(TestCase):
request = factory.get('/')
response = view(request)
self.assertEqual(
response.data,
[
{'id': 3, 'date': '2014-10-08', 'text': 'cde'},
{'id': 2, 'date': '2013-10-08', 'text': 'bcd'},
{'id': 1, 'date': '2012-10-08', 'text': 'abc'}
]
)
assert response.data == [
{'id': 3, 'date': '2014-10-08', 'text': 'cde'},
{'id': 2, 'date': '2013-10-08', 'text': 'bcd'},
{'id': 1, 'date': '2012-10-08', 'text': 'abc'}
]
class OrderingFilterTests(TestCase):
@ -713,14 +691,11 @@ class OrderingFilterTests(TestCase):
view = OrderingListView.as_view()
request = factory.get('/', {'ordering': 'text'})
response = view(request)
self.assertEqual(
response.data,
[
{'id': 1, 'title': 'zyx', 'text': 'abc'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 3, 'title': 'xwv', 'text': 'cde'},
]
)
assert response.data == [
{'id': 1, 'title': 'zyx', 'text': 'abc'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 3, 'title': 'xwv', 'text': 'cde'},
]
def test_reverse_ordering(self):
class OrderingListView(generics.ListAPIView):
@ -733,14 +708,11 @@ class OrderingFilterTests(TestCase):
view = OrderingListView.as_view()
request = factory.get('/', {'ordering': '-text'})
response = view(request)
self.assertEqual(
response.data,
[
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
)
assert response.data == [
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
def test_incorrectfield_ordering(self):
class OrderingListView(generics.ListAPIView):
@ -753,14 +725,11 @@ class OrderingFilterTests(TestCase):
view = OrderingListView.as_view()
request = factory.get('/', {'ordering': 'foobar'})
response = view(request)
self.assertEqual(
response.data,
[
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
)
assert response.data == [
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
def test_default_ordering(self):
class OrderingListView(generics.ListAPIView):
@ -773,14 +742,11 @@ class OrderingFilterTests(TestCase):
view = OrderingListView.as_view()
request = factory.get('')
response = view(request)
self.assertEqual(
response.data,
[
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
)
assert response.data == [
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
def test_default_ordering_using_string(self):
class OrderingListView(generics.ListAPIView):
@ -793,14 +759,11 @@ class OrderingFilterTests(TestCase):
view = OrderingListView.as_view()
request = factory.get('')
response = view(request)
self.assertEqual(
response.data,
[
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
)
assert response.data == [
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 1, 'title': 'zyx', 'text': 'abc'},
]
def test_ordering_by_aggregate_field(self):
# create some related models to aggregate order by
@ -824,14 +787,11 @@ class OrderingFilterTests(TestCase):
view = OrderingListView.as_view()
request = factory.get('/', {'ordering': 'relateds__count'})
response = view(request)
self.assertEqual(
response.data,
[
{'id': 1, 'title': 'zyx', 'text': 'abc'},
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
]
)
assert response.data == [
{'id': 1, 'title': 'zyx', 'text': 'abc'},
{'id': 3, 'title': 'xwv', 'text': 'cde'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
]
def test_ordering_with_nonstandard_ordering_param(self):
with override_settings(REST_FRAMEWORK={'ORDERING_PARAM': 'order'}):
@ -847,14 +807,11 @@ class OrderingFilterTests(TestCase):
view = OrderingListView.as_view()
request = factory.get('/', {'order': 'text'})
response = view(request)
self.assertEqual(
response.data,
[
{'id': 1, 'title': 'zyx', 'text': 'abc'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 3, 'title': 'xwv', 'text': 'cde'},
]
)
assert response.data == [
{'id': 1, 'title': 'zyx', 'text': 'abc'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 3, 'title': 'xwv', 'text': 'cde'},
]
reload_module(filters)
@ -884,14 +841,11 @@ class OrderingFilterTests(TestCase):
view = OrderingListView.as_view()
request = factory.get('/', {'ordering': 'text'})
response = view(request)
self.assertEqual(
response.data,
[
{'id': 1, 'title': 'zyx', 'text': 'abc'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 3, 'title': 'xwv', 'text': 'cde'},
]
)
assert response.data == [
{'id': 1, 'title': 'zyx', 'text': 'abc'},
{'id': 2, 'title': 'yxw', 'text': 'bcd'},
{'id': 3, 'title': 'xwv', 'text': 'cde'},
]
def test_ordering_with_improper_configuration(self):
class OrderingListView(generics.ListAPIView):
@ -967,14 +921,11 @@ class SensitiveOrderingFilterTests(TestCase):
username_field = 'username'
# Note: Inverse username ordering correctly applied.
self.assertEqual(
response.data,
[
{'id': 3, username_field: 'userC'},
{'id': 2, username_field: 'userB'},
{'id': 1, username_field: 'userA'},
]
)
assert response.data == [
{'id': 3, username_field: 'userC'},
{'id': 2, username_field: 'userB'},
{'id': 1, username_field: 'userA'},
]
def test_cannot_order_by_non_serializer_fields(self):
for serializer_cls in [
@ -997,11 +948,8 @@ class SensitiveOrderingFilterTests(TestCase):
username_field = 'username'
# Note: The passwords are not in order. Default ordering is used.
self.assertEqual(
response.data,
[
{'id': 1, username_field: 'userA'}, # PassB
{'id': 2, username_field: 'userB'}, # PassC
{'id': 3, username_field: 'userC'}, # PassA
]
)
assert response.data == [
{'id': 1, username_field: 'userA'}, # PassB
{'id': 2, username_field: 'userB'}, # PassC
{'id': 3, username_field: 'userC'}, # PassA
]

View File

@ -98,8 +98,8 @@ class TestRootView(TestCase):
request = factory.get('/')
with self.assertNumQueries(1):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data)
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data
def test_post_root_view(self):
"""
@ -109,10 +109,10 @@ class TestRootView(TestCase):
request = factory.post('/', data, format='json')
with self.assertNumQueries(1):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data, {'id': 4, 'text': 'foobar'})
assert response.status_code == status.HTTP_201_CREATED
assert response.data == {'id': 4, 'text': 'foobar'}
created = self.objects.get(id=4)
self.assertEqual(created.text, 'foobar')
assert created.text == 'foobar'
def test_put_root_view(self):
"""
@ -122,8 +122,8 @@ class TestRootView(TestCase):
request = factory.put('/', data, format='json')
with self.assertNumQueries(0):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
self.assertEqual(response.data, {"detail": 'Method "PUT" not allowed.'})
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
assert response.data == {"detail": 'Method "PUT" not allowed.'}
def test_delete_root_view(self):
"""
@ -132,8 +132,8 @@ class TestRootView(TestCase):
request = factory.delete('/')
with self.assertNumQueries(0):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
self.assertEqual(response.data, {"detail": 'Method "DELETE" not allowed.'})
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
assert response.data == {"detail": 'Method "DELETE" not allowed.'}
def test_post_cannot_set_id(self):
"""
@ -143,10 +143,10 @@ class TestRootView(TestCase):
request = factory.post('/', data, format='json')
with self.assertNumQueries(1):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data, {'id': 4, 'text': 'foobar'})
assert response.status_code == status.HTTP_201_CREATED
assert response.data == {'id': 4, 'text': 'foobar'}
created = self.objects.get(id=4)
self.assertEqual(created.text, 'foobar')
assert created.text == 'foobar'
def test_post_error_root_view(self):
"""
@ -156,7 +156,7 @@ class TestRootView(TestCase):
request = factory.post('/', data, HTTP_ACCEPT='text/html')
response = self.view(request).render()
expected_error = '<span class="help-block">Ensure this field has no more than 100 characters.</span>'
self.assertIn(expected_error, response.rendered_content.decode('utf-8'))
assert expected_error in response.rendered_content.decode('utf-8')
EXPECTED_QUERIES_FOR_PUT = 2
@ -185,8 +185,8 @@ class TestInstanceView(TestCase):
request = factory.get('/1')
with self.assertNumQueries(1):
response = self.view(request, pk=1).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data[0])
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data[0]
def test_post_instance_view(self):
"""
@ -196,8 +196,8 @@ class TestInstanceView(TestCase):
request = factory.post('/', data, format='json')
with self.assertNumQueries(0):
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
self.assertEqual(response.data, {"detail": 'Method "POST" not allowed.'})
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
assert response.data == {"detail": 'Method "POST" not allowed.'}
def test_put_instance_view(self):
"""
@ -207,10 +207,10 @@ class TestInstanceView(TestCase):
request = factory.put('/1', data, format='json')
with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT):
response = self.view(request, pk='1').render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(dict(response.data), {'id': 1, 'text': 'foobar'})
assert response.status_code == status.HTTP_200_OK
assert dict(response.data) == {'id': 1, 'text': 'foobar'}
updated = self.objects.get(id=1)
self.assertEqual(updated.text, 'foobar')
assert updated.text == 'foobar'
def test_patch_instance_view(self):
"""
@ -221,10 +221,10 @@ class TestInstanceView(TestCase):
with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT):
response = self.view(request, pk=1).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, {'id': 1, 'text': 'foobar'})
assert response.status_code == status.HTTP_200_OK
assert response.data == {'id': 1, 'text': 'foobar'}
updated = self.objects.get(id=1)
self.assertEqual(updated.text, 'foobar')
assert updated.text == 'foobar'
def test_delete_instance_view(self):
"""
@ -233,10 +233,10 @@ class TestInstanceView(TestCase):
request = factory.delete('/1')
with self.assertNumQueries(2):
response = self.view(request, pk=1).render()
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
self.assertEqual(response.content, six.b(''))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert response.content == six.b('')
ids = [obj.id for obj in self.objects.all()]
self.assertEqual(ids, [2, 3])
assert ids == [2, 3]
def test_get_instance_view_incorrect_arg(self):
"""
@ -246,7 +246,7 @@ class TestInstanceView(TestCase):
request = factory.get('/a')
with self.assertNumQueries(0):
response = self.view(request, pk='a').render()
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
assert response.status_code == status.HTTP_404_NOT_FOUND
def test_put_cannot_set_id(self):
"""
@ -256,10 +256,10 @@ class TestInstanceView(TestCase):
request = factory.put('/1', data, format='json')
with self.assertNumQueries(EXPECTED_QUERIES_FOR_PUT):
response = self.view(request, pk=1).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, {'id': 1, 'text': 'foobar'})
assert response.status_code == status.HTTP_200_OK
assert response.data == {'id': 1, 'text': 'foobar'}
updated = self.objects.get(id=1)
self.assertEqual(updated.text, 'foobar')
assert updated.text == 'foobar'
def test_put_to_deleted_instance(self):
"""
@ -271,7 +271,7 @@ class TestInstanceView(TestCase):
request = factory.put('/1', data, format='json')
with self.assertNumQueries(1):
response = self.view(request, pk=1).render()
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
assert response.status_code == status.HTTP_404_NOT_FOUND
def test_put_to_filtered_out_instance(self):
"""
@ -282,7 +282,7 @@ class TestInstanceView(TestCase):
filtered_out_pk = BasicModel.objects.filter(text='filtered out')[0].pk
request = factory.put('/{0}'.format(filtered_out_pk), data, format='json')
response = self.view(request, pk=filtered_out_pk).render()
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
assert response.status_code == status.HTTP_404_NOT_FOUND
def test_patch_cannot_create_an_object(self):
"""
@ -292,8 +292,8 @@ class TestInstanceView(TestCase):
request = factory.patch('/999', data, format='json')
with self.assertNumQueries(1):
response = self.view(request, pk=999).render()
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertFalse(self.objects.filter(id=999).exists())
assert response.status_code == status.HTTP_404_NOT_FOUND
assert not self.objects.filter(id=999).exists()
def test_put_error_instance_view(self):
"""
@ -303,7 +303,7 @@ class TestInstanceView(TestCase):
request = factory.put('/', data, HTTP_ACCEPT='text/html')
response = self.view(request, pk=1).render()
expected_error = '<span class="help-block">Ensure this field has no more than 100 characters.</span>'
self.assertIn(expected_error, response.rendered_content.decode('utf-8'))
assert expected_error in response.rendered_content.decode('utf-8')
class TestFKInstanceView(TestCase):
@ -363,8 +363,8 @@ class TestOverriddenGetObject(TestCase):
request = factory.get('/1')
with self.assertNumQueries(1):
response = self.view(request, pk=1).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, self.data[0])
assert response.status_code == status.HTTP_200_OK
assert response.data == self.data[0]
# Regression test for #285
@ -394,9 +394,9 @@ class TestCreateModelWithAutoNowAddField(TestCase):
data = {'email': 'foobar@example.com', 'content': 'foobar'}
request = factory.post('/', data, format='json')
response = self.view(request).render()
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
assert response.status_code == status.HTTP_201_CREATED
created = self.objects.get(id=1)
self.assertEqual(created.content, 'foobar')
assert created.content == 'foobar'
# Test for particularly ugly regression with m2m in browsable API
@ -432,7 +432,7 @@ class TestM2MBrowsableAPI(TestCase):
request = factory.get('/', HTTP_ACCEPT='text/html')
view = ExampleView().as_view()
response = view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
class InclusiveFilterBackend(object):
@ -489,9 +489,9 @@ class TestFilterBackendAppliedToViews(TestCase):
root_view = RootView.as_view(filter_backends=(InclusiveFilterBackend,))
request = factory.get('/')
response = root_view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 1)
self.assertEqual(response.data, [{'id': 1, 'text': 'foo'}])
assert response.status_code == status.HTTP_200_OK
assert len(response.data) == 1
assert response.data == [{'id': 1, 'text': 'foo'}]
def test_get_root_view_filters_out_all_models_with_exclusive_filter_backend(self):
"""
@ -500,8 +500,8 @@ class TestFilterBackendAppliedToViews(TestCase):
root_view = RootView.as_view(filter_backends=(ExclusiveFilterBackend,))
request = factory.get('/')
response = root_view(request).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, [])
assert response.status_code == status.HTTP_200_OK
assert response.data == []
def test_get_instance_view_filters_out_name_with_filter_backend(self):
"""
@ -510,8 +510,8 @@ class TestFilterBackendAppliedToViews(TestCase):
instance_view = InstanceView.as_view(filter_backends=(ExclusiveFilterBackend,))
request = factory.get('/1')
response = instance_view(request, pk=1).render()
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
self.assertEqual(response.data, {'detail': 'Not found.'})
assert response.status_code == status.HTTP_404_NOT_FOUND
assert response.data == {'detail': 'Not found.'}
def test_get_instance_view_will_return_single_object_when_filter_does_not_exclude_it(self):
"""
@ -520,8 +520,8 @@ class TestFilterBackendAppliedToViews(TestCase):
instance_view = InstanceView.as_view(filter_backends=(InclusiveFilterBackend,))
request = factory.get('/1')
response = instance_view(request, pk=1).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data, {'id': 1, 'text': 'foo'})
assert response.status_code == status.HTTP_200_OK
assert response.data == {'id': 1, 'text': 'foo'}
def test_dynamic_serializer_form_in_browsable_api(self):
"""
@ -530,8 +530,9 @@ class TestFilterBackendAppliedToViews(TestCase):
view = DynamicSerializerView.as_view()
request = factory.get('/')
response = view(request).render()
self.assertContains(response, 'field_b')
self.assertNotContains(response, 'field_a')
content = response.content.decode('utf8')
assert 'field_b' in content
assert 'field_a' not in content
class TestGuardedQueryset(TestCase):

View File

@ -94,7 +94,7 @@ class Issue3674ParentModel(models.Model):
class Issue3674ChildModel(models.Model):
parent = models.ForeignKey(Issue3674ParentModel, related_name='children')
parent = models.ForeignKey(Issue3674ParentModel, related_name='children', on_delete=models.CASCADE)
value = models.CharField(primary_key=True, max_length=64)
@ -1013,7 +1013,7 @@ class Issue3674Test(TestCase):
title = models.CharField(max_length=64)
class TestChildModel(models.Model):
parent = models.ForeignKey(TestParentModel, related_name='children')
parent = models.ForeignKey(TestParentModel, related_name='children', on_delete=models.CASCADE)
value = models.CharField(primary_key=True, max_length=64)
class TestChildModelSerializer(serializers.ModelSerializer):

View File

@ -44,8 +44,7 @@ class InheritedModelSerializationTests(TestCase):
"""
child = ChildModel(name1='parent name', name2='child name')
serializer = DerivedModelSerializer(child)
self.assertEqual(set(serializer.data.keys()),
set(['name1', 'name2', 'id']))
assert set(serializer.data.keys()) == set(['name1', 'name2', 'id'])
def test_onetoone_primary_key_model_fields_as_expected(self):
"""
@ -55,8 +54,7 @@ class InheritedModelSerializationTests(TestCase):
parent = ParentModel.objects.create(name1='parent name')
associate = AssociatedModel.objects.create(name='hello', ref=parent)
serializer = AssociatedModelSerializer(associate)
self.assertEqual(set(serializer.data.keys()),
set(['name', 'ref']))
assert set(serializer.data.keys()) == set(['name', 'ref'])
def test_data_is_valid_without_parent_ptr(self):
"""
@ -68,4 +66,4 @@ class InheritedModelSerializationTests(TestCase):
'name2': 'child name',
}
serializer = DerivedModelSerializer(data=data)
self.assertEqual(serializer.is_valid(), True)
assert serializer.is_valid() is True

View File

@ -38,20 +38,20 @@ class TestAcceptedMediaType(TestCase):
def test_client_without_accept_use_renderer(self):
request = Request(factory.get('/'))
accepted_renderer, accepted_media_type = self.select_renderer(request)
self.assertEqual(accepted_media_type, 'application/json')
assert accepted_media_type == 'application/json'
def test_client_underspecifies_accept_use_renderer(self):
request = Request(factory.get('/', HTTP_ACCEPT='*/*'))
accepted_renderer, accepted_media_type = self.select_renderer(request)
self.assertEqual(accepted_media_type, 'application/json')
assert accepted_media_type == 'application/json'
def test_client_overspecifies_accept_use_client(self):
request = Request(factory.get('/', HTTP_ACCEPT='application/json; indent=8'))
accepted_renderer, accepted_media_type = self.select_renderer(request)
self.assertEqual(accepted_media_type, 'application/json; indent=8')
assert accepted_media_type == 'application/json; indent=8'
def test_client_specifies_parameter(self):
request = Request(factory.get('/', HTTP_ACCEPT='application/openapi+json;version=2.0'))
accepted_renderer, accepted_media_type = self.select_renderer(request)
self.assertEqual(accepted_media_type, 'application/openapi+json;version=2.0')
self.assertEqual(accepted_renderer.format, 'swagger')
assert accepted_media_type == 'application/openapi+json;version=2.0'
assert accepted_renderer.format == 'swagger'

View File

@ -2,6 +2,7 @@ from django.contrib.auth.models import Group, User
from django.test import TestCase
from rest_framework import generics, serializers
from rest_framework.compat import set_many
from rest_framework.test import APIRequestFactory
factory = APIRequestFactory()
@ -22,7 +23,7 @@ class TestPrefetchRelatedUpdates(TestCase):
def setUp(self):
self.user = User.objects.create(username='tom', email='tom@example.com')
self.groups = [Group.objects.create(name='a'), Group.objects.create(name='b')]
self.user.groups = self.groups
set_many(self.user, 'groups', self.groups)
self.user.save()
def test_prefetch_related_updates(self):

View File

@ -75,7 +75,7 @@ class TestGenericRelations(TestCase):
'tags': ['django', 'python'],
'url': 'https://www.djangoproject.com/'
}
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_generic_fk(self):
"""
@ -105,4 +105,4 @@ class TestGenericRelations(TestCase):
'tagged_item': 'Note: Remember the milk'
}
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected

View File

@ -84,7 +84,7 @@ class PKManyToManyTests(TestCase):
{'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]}
]
with self.assertNumQueries(4):
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_many_to_many_retrieve_prefetch_related(self):
queryset = ManyToManySource.objects.all().prefetch_related('targets')
@ -101,15 +101,15 @@ class PKManyToManyTests(TestCase):
{'id': 3, 'name': 'target-3', 'sources': [3]}
]
with self.assertNumQueries(4):
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_many_to_many_update(self):
data = {'id': 1, 'name': 'source-1', 'targets': [1, 2, 3]}
instance = ManyToManySource.objects.get(pk=1)
serializer = ManyToManySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
serializer.save()
self.assertEqual(serializer.data, data)
assert serializer.data == data
# Ensure source 1 is updated, and everything else is as expected
queryset = ManyToManySource.objects.all()
@ -119,15 +119,15 @@ class PKManyToManyTests(TestCase):
{'id': 2, 'name': 'source-2', 'targets': [1, 2]},
{'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]}
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_reverse_many_to_many_update(self):
data = {'id': 1, 'name': 'target-1', 'sources': [1]}
instance = ManyToManyTarget.objects.get(pk=1)
serializer = ManyToManyTargetSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
serializer.save()
self.assertEqual(serializer.data, data)
assert serializer.data == data
# Ensure target 1 is updated, and everything else is as expected
queryset = ManyToManyTarget.objects.all()
@ -137,15 +137,15 @@ class PKManyToManyTests(TestCase):
{'id': 2, 'name': 'target-2', 'sources': [2, 3]},
{'id': 3, 'name': 'target-3', 'sources': [3]}
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_many_to_many_create(self):
data = {'id': 4, 'name': 'source-4', 'targets': [1, 3]}
serializer = ManyToManySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
obj = serializer.save()
self.assertEqual(serializer.data, data)
self.assertEqual(obj.name, 'source-4')
assert serializer.data == data
assert obj.name == 'source-4'
# Ensure source 4 is added, and everything else is as expected
queryset = ManyToManySource.objects.all()
@ -156,7 +156,7 @@ class PKManyToManyTests(TestCase):
{'id': 3, 'name': 'source-3', 'targets': [1, 2, 3]},
{'id': 4, 'name': 'source-4', 'targets': [1, 3]},
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_many_to_many_unsaved(self):
source = ManyToManySource(name='source-unsaved')
@ -166,15 +166,15 @@ class PKManyToManyTests(TestCase):
expected = {'id': None, 'name': 'source-unsaved', 'targets': []}
# no query if source hasn't been created yet
with self.assertNumQueries(0):
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_reverse_many_to_many_create(self):
data = {'id': 4, 'name': 'target-4', 'sources': [1, 3]}
serializer = ManyToManyTargetSerializer(data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
obj = serializer.save()
self.assertEqual(serializer.data, data)
self.assertEqual(obj.name, 'target-4')
assert serializer.data == data
assert obj.name == 'target-4'
# Ensure target 4 is added, and everything else is as expected
queryset = ManyToManyTarget.objects.all()
@ -185,7 +185,7 @@ class PKManyToManyTests(TestCase):
{'id': 3, 'name': 'target-3', 'sources': [3]},
{'id': 4, 'name': 'target-4', 'sources': [1, 3]}
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
class PKForeignKeyTests(TestCase):
@ -207,7 +207,7 @@ class PKForeignKeyTests(TestCase):
{'id': 3, 'name': 'source-3', 'target': 1}
]
with self.assertNumQueries(1):
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_reverse_foreign_key_retrieve(self):
queryset = ForeignKeyTarget.objects.all()
@ -217,7 +217,7 @@ class PKForeignKeyTests(TestCase):
{'id': 2, 'name': 'target-2', 'sources': []},
]
with self.assertNumQueries(3):
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_reverse_foreign_key_retrieve_prefetch_related(self):
queryset = ForeignKeyTarget.objects.all().prefetch_related('sources')
@ -229,9 +229,9 @@ class PKForeignKeyTests(TestCase):
data = {'id': 1, 'name': 'source-1', 'target': 2}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
serializer.save()
self.assertEqual(serializer.data, data)
assert serializer.data == data
# Ensure source 1 is updated, and everything else is as expected
queryset = ForeignKeySource.objects.all()
@ -241,20 +241,20 @@ class PKForeignKeyTests(TestCase):
{'id': 2, 'name': 'source-2', 'target': 1},
{'id': 3, 'name': 'source-3', 'target': 1}
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_foreign_key_update_incorrect_type(self):
data = {'id': 1, 'name': 'source-1', 'target': 'foo'}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertFalse(serializer.is_valid())
self.assertEqual(serializer.errors, {'target': ['Incorrect type. Expected pk value, received %s.' % six.text_type.__name__]})
assert not serializer.is_valid()
assert serializer.errors == {'target': ['Incorrect type. Expected pk value, received %s.' % six.text_type.__name__]}
def test_reverse_foreign_key_update(self):
data = {'id': 2, 'name': 'target-2', 'sources': [1, 3]}
instance = ForeignKeyTarget.objects.get(pk=2)
serializer = ForeignKeyTargetSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
# We shouldn't have saved anything to the db yet since save
# hasn't been called.
queryset = ForeignKeyTarget.objects.all()
@ -263,10 +263,10 @@ class PKForeignKeyTests(TestCase):
{'id': 1, 'name': 'target-1', 'sources': [1, 2, 3]},
{'id': 2, 'name': 'target-2', 'sources': []},
]
self.assertEqual(new_serializer.data, expected)
assert new_serializer.data == expected
serializer.save()
self.assertEqual(serializer.data, data)
assert serializer.data == data
# Ensure target 2 is update, and everything else is as expected
queryset = ForeignKeyTarget.objects.all()
@ -275,15 +275,15 @@ class PKForeignKeyTests(TestCase):
{'id': 1, 'name': 'target-1', 'sources': [2]},
{'id': 2, 'name': 'target-2', 'sources': [1, 3]},
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_foreign_key_create(self):
data = {'id': 4, 'name': 'source-4', 'target': 2}
serializer = ForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
obj = serializer.save()
self.assertEqual(serializer.data, data)
self.assertEqual(obj.name, 'source-4')
assert serializer.data == data
assert obj.name == 'source-4'
# Ensure source 4 is added, and everything else is as expected
queryset = ForeignKeySource.objects.all()
@ -294,15 +294,15 @@ class PKForeignKeyTests(TestCase):
{'id': 3, 'name': 'source-3', 'target': 1},
{'id': 4, 'name': 'source-4', 'target': 2},
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_reverse_foreign_key_create(self):
data = {'id': 3, 'name': 'target-3', 'sources': [1, 3]}
serializer = ForeignKeyTargetSerializer(data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
obj = serializer.save()
self.assertEqual(serializer.data, data)
self.assertEqual(obj.name, 'target-3')
assert serializer.data == data
assert obj.name == 'target-3'
# Ensure target 3 is added, and everything else is as expected
queryset = ForeignKeyTarget.objects.all()
@ -312,14 +312,14 @@ class PKForeignKeyTests(TestCase):
{'id': 2, 'name': 'target-2', 'sources': []},
{'id': 3, 'name': 'target-3', 'sources': [1, 3]},
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_foreign_key_update_with_invalid_null(self):
data = {'id': 1, 'name': 'source-1', 'target': None}
instance = ForeignKeySource.objects.get(pk=1)
serializer = ForeignKeySourceSerializer(instance, data=data)
self.assertFalse(serializer.is_valid())
self.assertEqual(serializer.errors, {'target': ['This field may not be null.']})
assert not serializer.is_valid()
assert serializer.errors == {'target': ['This field may not be null.']}
def test_foreign_key_with_unsaved(self):
source = ForeignKeySource(name='source-unsaved')
@ -329,7 +329,7 @@ class PKForeignKeyTests(TestCase):
# no query if source hasn't been created yet
with self.assertNumQueries(0):
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_foreign_key_with_empty(self):
"""
@ -338,7 +338,7 @@ class PKForeignKeyTests(TestCase):
https://github.com/tomchristie/django-rest-framework/issues/1072
"""
serializer = NullableForeignKeySourceSerializer()
self.assertEqual(serializer.data['target'], None)
assert serializer.data['target'] is None
def test_foreign_key_not_required(self):
"""
@ -350,7 +350,7 @@ class PKForeignKeyTests(TestCase):
extra_kwargs = {'target': {'required': False}}
serializer = ModelSerializer(data={'name': 'test'})
serializer.is_valid(raise_exception=True)
self.assertNotIn('target', serializer.validated_data)
assert 'target' not in serializer.validated_data
class PKNullableForeignKeyTests(TestCase):
@ -371,15 +371,15 @@ class PKNullableForeignKeyTests(TestCase):
{'id': 2, 'name': 'source-2', 'target': 1},
{'id': 3, 'name': 'source-3', 'target': None},
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_foreign_key_create_with_valid_null(self):
data = {'id': 4, 'name': 'source-4', 'target': None}
serializer = NullableForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
obj = serializer.save()
self.assertEqual(serializer.data, data)
self.assertEqual(obj.name, 'source-4')
assert serializer.data == data
assert obj.name == 'source-4'
# Ensure source 4 is created, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
@ -390,7 +390,7 @@ class PKNullableForeignKeyTests(TestCase):
{'id': 3, 'name': 'source-3', 'target': None},
{'id': 4, 'name': 'source-4', 'target': None}
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_foreign_key_create_with_valid_emptystring(self):
"""
@ -400,10 +400,10 @@ class PKNullableForeignKeyTests(TestCase):
data = {'id': 4, 'name': 'source-4', 'target': ''}
expected_data = {'id': 4, 'name': 'source-4', 'target': None}
serializer = NullableForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
obj = serializer.save()
self.assertEqual(serializer.data, expected_data)
self.assertEqual(obj.name, 'source-4')
assert serializer.data == expected_data
assert obj.name == 'source-4'
# Ensure source 4 is created, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
@ -414,15 +414,15 @@ class PKNullableForeignKeyTests(TestCase):
{'id': 3, 'name': 'source-3', 'target': None},
{'id': 4, 'name': 'source-4', 'target': None}
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_foreign_key_update_with_valid_null(self):
data = {'id': 1, 'name': 'source-1', 'target': None}
instance = NullableForeignKeySource.objects.get(pk=1)
serializer = NullableForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
serializer.save()
self.assertEqual(serializer.data, data)
assert serializer.data == data
# Ensure source 1 is updated, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
@ -432,7 +432,7 @@ class PKNullableForeignKeyTests(TestCase):
{'id': 2, 'name': 'source-2', 'target': 1},
{'id': 3, 'name': 'source-3', 'target': None}
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_foreign_key_update_with_valid_emptystring(self):
"""
@ -443,9 +443,9 @@ class PKNullableForeignKeyTests(TestCase):
expected_data = {'id': 1, 'name': 'source-1', 'target': None}
instance = NullableForeignKeySource.objects.get(pk=1)
serializer = NullableForeignKeySourceSerializer(instance, data=data)
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
serializer.save()
self.assertEqual(serializer.data, expected_data)
assert serializer.data == expected_data
# Ensure source 1 is updated, and everything else is as expected
queryset = NullableForeignKeySource.objects.all()
@ -455,18 +455,18 @@ class PKNullableForeignKeyTests(TestCase):
{'id': 2, 'name': 'source-2', 'target': 1},
{'id': 3, 'name': 'source-3', 'target': None}
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected
def test_null_uuid_foreign_key_serializes_as_none(self):
source = NullableUUIDForeignKeySource(name='Source')
serializer = NullableUUIDForeignKeySourceSerializer(source)
data = serializer.data
self.assertEqual(data["target"], None)
assert data["target"] is None
def test_nullable_uuid_foreign_key_is_valid_when_none(self):
data = {"name": "Source", "target": None}
serializer = NullableUUIDForeignKeySourceSerializer(data=data)
self.assertTrue(serializer.is_valid(), serializer.errors)
assert serializer.is_valid(), serializer.errors
class PKNullableOneToOneTests(TestCase):
@ -485,4 +485,4 @@ class PKNullableOneToOneTests(TestCase):
{'id': 1, 'name': 'target-1', 'nullable_source': None},
{'id': 2, 'name': 'target-2', 'nullable_source': 1},
]
self.assertEqual(serializer.data, expected)
assert serializer.data == expected

View File

@ -7,6 +7,8 @@ import re
import pytest
from django.db import models
from rest_framework import fields, relations, serializers
from rest_framework.compat import unicode_repr
from rest_framework.fields import Field
@ -413,3 +415,42 @@ class Test4606Regression:
serializer = self.Serializer(data=[{"name": "liz"}], many=True)
with pytest.raises(serializers.ValidationError):
serializer.is_valid(raise_exception=True)
class TestDeclaredFieldInheritance:
def test_declared_field_disabling(self):
class Parent(serializers.Serializer):
f1 = serializers.CharField()
f2 = serializers.CharField()
class Child(Parent):
f1 = None
class Grandchild(Child):
pass
assert len(Parent._declared_fields) == 2
assert len(Child._declared_fields) == 1
assert len(Grandchild._declared_fields) == 1
def test_meta_field_disabling(self):
# Declaratively setting a field on a child class will *not* prevent
# the ModelSerializer from generating a default field.
class MyModel(models.Model):
f1 = models.CharField(max_length=10)
f2 = models.CharField(max_length=10)
class Parent(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ['f1', 'f2']
class Child(Parent):
f1 = None
class Grandchild(Child):
pass
assert len(Parent().get_fields()) == 2
assert len(Child().get_fields()) == 2
assert len(Grandchild().get_fields()) == 2

View File

@ -70,7 +70,7 @@ class ThrottlingTests(TestCase):
request = self.factory.get('/')
for dummy in range(4):
response = MockView.as_view()(request)
self.assertEqual(429, response.status_code)
assert response.status_code == 429
def set_throttle_timer(self, view, value):
"""
@ -87,13 +87,13 @@ class ThrottlingTests(TestCase):
request = self.factory.get('/')
for dummy in range(4):
response = MockView.as_view()(request)
self.assertEqual(429, response.status_code)
assert response.status_code == 429
# Advance the timer by one second
self.set_throttle_timer(MockView, 1)
response = MockView.as_view()(request)
self.assertEqual(200, response.status_code)
assert response.status_code == 200
def ensure_is_throttled(self, view, expect):
request = self.factory.get('/')
@ -102,7 +102,7 @@ class ThrottlingTests(TestCase):
view.as_view()(request)
request.user = User.objects.create(username='b')
response = view.as_view()(request)
self.assertEqual(expect, response.status_code)
assert response.status_code == expect
def test_request_throttling_is_per_user(self):
"""
@ -121,9 +121,9 @@ class ThrottlingTests(TestCase):
self.set_throttle_timer(view, timer)
response = view.as_view()(request)
if expect is not None:
self.assertEqual(response['Retry-After'], expect)
assert response['Retry-After'] == expect
else:
self.assertFalse('Retry-After' in response)
assert not'Retry-After' in response
def test_seconds_fields(self):
"""
@ -230,56 +230,55 @@ class ScopedRateThrottleTests(TestCase):
# Should be able to hit x view 3 times per minute.
response = self.x_view(request)
self.assertEqual(200, response.status_code)
assert response.status_code == 200
self.increment_timer()
response = self.x_view(request)
self.assertEqual(200, response.status_code)
assert response.status_code == 200
self.increment_timer()
response = self.x_view(request)
self.assertEqual(200, response.status_code)
assert response.status_code == 200
self.increment_timer()
response = self.x_view(request)
self.assertEqual(429, response.status_code)
assert response.status_code == 429
# Should be able to hit y view 1 time per minute.
self.increment_timer()
response = self.y_view(request)
self.assertEqual(200, response.status_code)
assert response.status_code == 200
self.increment_timer()
response = self.y_view(request)
self.assertEqual(429, response.status_code)
assert response.status_code == 429
# Ensure throttles properly reset by advancing the rest of the minute
self.increment_timer(55)
# Should still be able to hit x view 3 times per minute.
response = self.x_view(request)
self.assertEqual(200, response.status_code)
assert response.status_code == 200
self.increment_timer()
response = self.x_view(request)
self.assertEqual(200, response.status_code)
assert response.status_code == 200
self.increment_timer()
response = self.x_view(request)
self.assertEqual(200, response.status_code)
assert response.status_code == 200
self.increment_timer()
response = self.x_view(request)
self.assertEqual(429, response.status_code)
assert response.status_code == 429
# Should still be able to hit y view 1 time per minute.
self.increment_timer()
response = self.y_view(request)
self.assertEqual(200, response.status_code)
assert response.status_code == 200
self.increment_timer()
response = self.y_view(request)
self.assertEqual(429, response.status_code)
assert response.status_code == 429
def test_unscoped_view_not_throttled(self):
request = self.factory.get('/')
@ -287,7 +286,7 @@ class ScopedRateThrottleTests(TestCase):
for idx in range(10):
self.increment_timer()
response = self.unscoped_view(request)
self.assertEqual(200, response.status_code)
assert response.status_code == 200
class XffTestingBase(TestCase):
@ -321,12 +320,12 @@ class XffTestingBase(TestCase):
class IdWithXffBasicTests(XffTestingBase):
def test_accepts_request_under_limit(self):
self.config_proxy(0)
self.assertEqual(200, self.view(self.request).status_code)
assert self.view(self.request).status_code == 200
def test_denies_request_over_limit(self):
self.config_proxy(0)
self.view(self.request)
self.assertEqual(429, self.view(self.request).status_code)
assert self.view(self.request).status_code == 429
class XffSpoofingTests(XffTestingBase):
@ -334,13 +333,13 @@ class XffSpoofingTests(XffTestingBase):
self.config_proxy(1)
self.view(self.request)
self.request.META['HTTP_X_FORWARDED_FOR'] = '4.4.4.4, 5.5.5.5, 2.2.2.2'
self.assertEqual(429, self.view(self.request).status_code)
assert self.view(self.request).status_code == 429
def test_xff_spoofing_doesnt_change_machine_id_with_two_app_proxies(self):
self.config_proxy(2)
self.view(self.request)
self.request.META['HTTP_X_FORWARDED_FOR'] = '4.4.4.4, 1.1.1.1, 2.2.2.2'
self.assertEqual(429, self.view(self.request).status_code)
assert self.view(self.request).status_code == 429
class XffUniqueMachinesTest(XffTestingBase):
@ -348,10 +347,10 @@ class XffUniqueMachinesTest(XffTestingBase):
self.config_proxy(1)
self.view(self.request)
self.request.META['HTTP_X_FORWARDED_FOR'] = '0.0.0.0, 1.1.1.1, 7.7.7.7'
self.assertEqual(200, self.view(self.request).status_code)
assert self.view(self.request).status_code == 200
def test_unique_clients_are_counted_independently_with_two_proxies(self):
self.config_proxy(2)
self.view(self.request)
self.request.META['HTTP_X_FORWARDED_FOR'] = '0.0.0.0, 7.7.7.7, 2.2.2.2'
self.assertEqual(200, self.view(self.request).status_code)
assert self.view(self.request).status_code == 200

View File

@ -35,8 +35,8 @@ class FormatSuffixTests(TestCase):
callback, callback_args, callback_kwargs = resolver.resolve(request.path_info)
except Exception:
self.fail("Failed to resolve URL: %s" % request.path_info)
self.assertEqual(callback_args, test_path.args)
self.assertEqual(callback_kwargs, test_path.kwargs)
assert callback_args == test_path.args
assert callback_kwargs == test_path.kwargs
def test_trailing_slash(self):
factory = APIRequestFactory()

View File

@ -7,8 +7,11 @@ from django.utils import six
import rest_framework.utils.model_meta
from rest_framework.compat import _resolve_model
from rest_framework.routers import SimpleRouter
from rest_framework.serializers import ModelSerializer
from rest_framework.utils.breadcrumbs import get_breadcrumbs
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet
from tests.models import BasicModel
@ -37,6 +40,13 @@ class CustomNameResourceInstance(APIView):
return "Foo"
class ResourceViewSet(ModelViewSet):
serializer_class = ModelSerializer
queryset = BasicModel.objects.all()
router = SimpleRouter()
router.register(r'resources', ResourceViewSet)
urlpatterns = [
url(r'^$', Root.as_view()),
url(r'^resource/$', ResourceRoot.as_view()),
@ -45,6 +55,7 @@ urlpatterns = [
url(r'^resource/(?P<key>[0-9]+)/$', NestedResourceRoot.as_view()),
url(r'^resource/(?P<key>[0-9]+)/(?P<other>[A-Za-z]+)$', NestedResourceInstance.as_view()),
]
urlpatterns += router.urls
@override_settings(ROOT_URLCONF='tests.test_utils')
@ -54,74 +65,60 @@ class BreadcrumbTests(TestCase):
"""
def test_root_breadcrumbs(self):
url = '/'
self.assertEqual(
get_breadcrumbs(url),
[('Root', '/')]
)
assert get_breadcrumbs(url) == [('Root', '/')]
def test_resource_root_breadcrumbs(self):
url = '/resource/'
self.assertEqual(
get_breadcrumbs(url),
[
('Root', '/'),
('Resource Root', '/resource/')
]
)
assert get_breadcrumbs(url) == [
('Root', '/'), ('Resource Root', '/resource/')
]
def test_resource_instance_breadcrumbs(self):
url = '/resource/123'
self.assertEqual(
get_breadcrumbs(url),
[
('Root', '/'),
('Resource Root', '/resource/'),
('Resource Instance', '/resource/123')
]
)
assert get_breadcrumbs(url) == [
('Root', '/'),
('Resource Root', '/resource/'),
('Resource Instance', '/resource/123')
]
def test_resource_instance_customname_breadcrumbs(self):
url = '/resource/customname'
self.assertEqual(
get_breadcrumbs(url),
[
('Root', '/'),
('Resource Root', '/resource/'),
('Foo', '/resource/customname')
]
)
assert get_breadcrumbs(url) == [
('Root', '/'),
('Resource Root', '/resource/'),
('Foo', '/resource/customname')
]
def test_nested_resource_breadcrumbs(self):
url = '/resource/123/'
self.assertEqual(
get_breadcrumbs(url),
[
('Root', '/'),
('Resource Root', '/resource/'),
('Resource Instance', '/resource/123'),
('Nested Resource Root', '/resource/123/')
]
)
assert get_breadcrumbs(url) == [
('Root', '/'),
('Resource Root', '/resource/'),
('Resource Instance', '/resource/123'),
('Nested Resource Root', '/resource/123/')
]
def test_nested_resource_instance_breadcrumbs(self):
url = '/resource/123/abc'
self.assertEqual(
get_breadcrumbs(url),
[
('Root', '/'),
('Resource Root', '/resource/'),
('Resource Instance', '/resource/123'),
('Nested Resource Root', '/resource/123/'),
('Nested Resource Instance', '/resource/123/abc')
]
)
assert get_breadcrumbs(url) == [
('Root', '/'),
('Resource Root', '/resource/'),
('Resource Instance', '/resource/123'),
('Nested Resource Root', '/resource/123/'),
('Nested Resource Instance', '/resource/123/abc')
]
def test_broken_url_breadcrumbs_handled_gracefully(self):
url = '/foobar'
self.assertEqual(
get_breadcrumbs(url),
[('Root', '/')]
)
assert get_breadcrumbs(url) == [('Root', '/')]
def test_modelviewset_resource_instance_breadcrumbs(self):
url = '/resources/1/'
assert get_breadcrumbs(url) == [
('Root', '/'),
('Resource List', '/resources/'),
('Resource Instance', '/resources/1/')
]
class ResolveModelTests(TestCase):
@ -132,15 +129,15 @@ class ResolveModelTests(TestCase):
"""
def test_resolve_django_model(self):
resolved_model = _resolve_model(BasicModel)
self.assertEqual(resolved_model, BasicModel)
assert resolved_model == BasicModel
def test_resolve_string_representation(self):
resolved_model = _resolve_model('tests.BasicModel')
self.assertEqual(resolved_model, BasicModel)
assert resolved_model == BasicModel
def test_resolve_unicode_representation(self):
resolved_model = _resolve_model(six.text_type('tests.BasicModel'))
self.assertEqual(resolved_model, BasicModel)
assert resolved_model == BasicModel
def test_resolve_non_django_model(self):
with self.assertRaises(ValueError):

View File

@ -60,11 +60,11 @@ class TestNestedValidationError(TestCase):
}
})
self.assertEqual(serializers.as_serializer_error(e), {
assert serializers.as_serializer_error(e) == {
'nested': {
'field': ['error'],
}
})
}
class TestPreSaveValidationExclusionsSerializer(TestCase):
@ -75,20 +75,20 @@ class TestPreSaveValidationExclusionsSerializer(TestCase):
# We've set `required=False` on the serializer, but the model
# does not have `blank=True`, so this serializer should not validate.
serializer = ShouldValidateModelSerializer(data={'renamed': ''})
self.assertEqual(serializer.is_valid(), False)
self.assertIn('renamed', serializer.errors)
self.assertNotIn('should_validate_field', serializer.errors)
assert serializer.is_valid() is False
assert 'renamed' in serializer.errors
assert 'should_validate_field' not in serializer.errors
class TestCustomValidationMethods(TestCase):
def test_custom_validation_method_is_executed(self):
serializer = ShouldValidateModelSerializer(data={'renamed': 'fo'})
self.assertFalse(serializer.is_valid())
self.assertIn('renamed', serializer.errors)
assert not serializer.is_valid()
assert 'renamed' in serializer.errors
def test_custom_validation_method_passing(self):
serializer = ShouldValidateModelSerializer(data={'renamed': 'foo'})
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
class ValidationSerializer(serializers.Serializer):
@ -108,12 +108,12 @@ class TestAvoidValidation(TestCase):
"""
def test_serializer_errors_has_only_invalid_data_error(self):
serializer = ValidationSerializer(data='invalid data')
self.assertFalse(serializer.is_valid())
self.assertDictEqual(serializer.errors, {
assert not serializer.is_valid()
assert serializer.errors == {
'non_field_errors': [
'Invalid data. Expected a dictionary, but got %s.' % type('').__name__
]
})
}
# regression tests for issue: 1493
@ -137,27 +137,31 @@ class TestMaxValueValidatorValidation(TestCase):
def test_max_value_validation_serializer_success(self):
serializer = ValidationMaxValueValidatorModelSerializer(data={'number_value': 99})
self.assertTrue(serializer.is_valid())
assert serializer.is_valid()
def test_max_value_validation_serializer_fails(self):
serializer = ValidationMaxValueValidatorModelSerializer(data={'number_value': 101})
self.assertFalse(serializer.is_valid())
self.assertDictEqual({'number_value': ['Ensure this value is less than or equal to 100.']}, serializer.errors)
assert not serializer.is_valid()
assert serializer.errors == {
'number_value': [
'Ensure this value is less than or equal to 100.'
]
}
def test_max_value_validation_success(self):
obj = ValidationMaxValueValidatorModel.objects.create(number_value=100)
request = factory.patch('/{0}'.format(obj.pk), {'number_value': 98}, format='json')
view = UpdateMaxValueValidationModel().as_view()
response = view(request, pk=obj.pk).render()
self.assertEqual(response.status_code, status.HTTP_200_OK)
assert response.status_code == status.HTTP_200_OK
def test_max_value_validation_fail(self):
obj = ValidationMaxValueValidatorModel.objects.create(number_value=100)
request = factory.patch('/{0}'.format(obj.pk), {'number_value': 101}, format='json')
view = UpdateMaxValueValidationModel().as_view()
response = view(request, pk=obj.pk).render()
self.assertEqual(response.content, b'{"number_value":["Ensure this value is less than or equal to 100."]}')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
assert response.content == b'{"number_value":["Ensure this value is less than or equal to 100."]}'
assert response.status_code == status.HTTP_400_BAD_REQUEST
# regression tests for issue: 1533

View File

@ -54,16 +54,16 @@ class TestValidationErrorWithFullDetails(TestCase):
request = factory.get('/', content_type='application/json')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, self.expected_response_data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data == self.expected_response_data
def test_function_based_view_exception_handler(self):
view = error_view
request = factory.get('/', content_type='application/json')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, self.expected_response_data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data == self.expected_response_data
class TestValidationErrorWithCodes(TestCase):
@ -89,13 +89,13 @@ class TestValidationErrorWithCodes(TestCase):
request = factory.get('/', content_type='application/json')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, self.expected_response_data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data == self.expected_response_data
def test_function_based_view_exception_handler(self):
view = error_view
request = factory.get('/', content_type='application/json')
response = view(request)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(response.data, self.expected_response_data)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.data == self.expected_response_data

View File

@ -15,9 +15,9 @@ setenv =
PYTHONDONTWRITEBYTECODE=1
PYTHONWARNINGS=once
deps =
django18: Django==1.8.16
django19: Django==1.9.11
django110: Django==1.10.3
django18: Django==1.8.17
django19: Django==1.9.12
django110: Django==1.10.5
djangomaster: https://github.com/django/django/archive/master.tar.gz
-rrequirements/requirements-testing.txt
-rrequirements/requirements-optionals.txt