mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-08-05 13:00:12 +03:00
Merge branch 'master' into docs
This commit is contained in:
commit
98936655f4
|
@ -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>
|
||||
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 child’s `Meta`, if it exists, otherwise the `Meta` of the first parent, etc.
|
||||
* It’s 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 won’t 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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)
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
#### 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
]
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
6
tox.ini
6
tox.ini
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user