fixed merge conflicts

This commit is contained in:
Asif Saif Uddin 2019-03-04 12:01:02 +06:00
commit 7e0d41608c
106 changed files with 1037 additions and 327 deletions

View File

@ -1,8 +1,6 @@
language: python
cache: pip
sudo: false
dist: xenial
matrix:
fast_finish: true
include:
@ -13,22 +11,25 @@ matrix:
- { python: "3.5", env: DJANGO=1.11 }
- { python: "3.5", env: DJANGO=2.0 }
- { python: "3.5", env: DJANGO=2.1 }
- { python: "3.5", env: DJANGO=master }
- { python: "3.5", env: DJANGO=2.2 }
- { python: "3.6", env: DJANGO=1.11 }
- { python: "3.6", env: DJANGO=2.0 }
- { python: "3.6", env: DJANGO=2.1 }
- { python: "3.6", env: DJANGO=2.2 }
- { python: "3.6", env: DJANGO=master }
- { python: "3.7", env: DJANGO=2.0, dist: xenial, sudo: true }
- { python: "3.7", env: DJANGO=2.1, dist: xenial, sudo: true }
- { python: "3.7", env: DJANGO=master, dist: xenial, sudo: true }
- { python: "3.7", env: DJANGO=2.0 }
- { python: "3.7", env: DJANGO=2.1 }
- { python: "3.7", env: DJANGO=2.2 }
- { python: "3.7", env: DJANGO=master }
- { python: "3.6", env: TOXENV=base }
- { python: "3.6", env: TOXENV=lint }
- { python: "3.6", env: TOXENV=docs }
- python: "3.6"
- python: "3.7"
env: TOXENV=dist
script:
- python setup.py bdist_wheel

View File

@ -20,7 +20,7 @@ When answering questions make sure to help future contributors find their way ar
Please keep the tone polite & professional. For some users a discussion on the REST framework mailing list or ticket tracker may be their first engagement with the open source community. First impressions count, so let's try to make everyone feel welcome.
Be mindful in the language you choose. As an example, in an environment that is heavily male-dominated, posts that start 'Hey guys,' can come across as unintentionally exclusive. It's just as easy, and more inclusive to use gender neutral language in those situations.
Be mindful in the language you choose. As an example, in an environment that is heavily male-dominated, posts that start 'Hey guys,' can come across as unintentionally exclusive. It's just as easy, and more inclusive to use gender neutral language in those situations. (e.g. 'Hey folks,')
The [Django code of conduct][code-of-conduct] gives a fuller set of guidelines for participating in community forums.
@ -50,7 +50,7 @@ Getting involved in triaging incoming issues is a good way to start contributing
To start developing on Django REST framework, clone the repo:
git clone git@github.com:encode/django-rest-framework.git
git clone https://github.com/encode/django-rest-framework
Changes should broadly follow the [PEP 8][pep-8] style conventions, and we recommend you set up your editor to automatically indicate non-conforming styles.

View File

@ -3,7 +3,7 @@
- [ ] I have verified that that issue exists against the `master` branch of Django REST framework.
- [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
- [ ] This is not a usage question. (Those should be directed to the [discussion group](https://groups.google.com/forum/#!forum/django-rest-framework) instead.)
- [ ] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/topics/third-party-resources/#about-third-party-packages) where possible.)
- [ ] This cannot be dealt with as a third party library. (We prefer new functionality to be [in the form of third party libraries](https://www.django-rest-framework.org/community/third-party-packages/#about-third-party-packages) where possible.)
- [ ] I have reduced the issue to the simplest possible case.
- [ ] I have included a failing test as a pull request. (If you are unable to do so we can still accept the issue.)

View File

@ -27,8 +27,9 @@ The initial aim is to provide a single full-time position on REST framework.
[![][load-impact-img]][load-impact-url]
[![][kloudless-img]][kloudless-url]
[![][auklet-img]][auklet-url]
[![][lightson-img]][lightson-url]
Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover][rover-url], [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [Cadre][cadre-url], [Load Impact][load-impact-url], [Kloudless][kloudless-url], and [Auklet][auklet-url].
Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover][rover-url], [Sentry][sentry-url], [Stream][stream-url], [Rollbar][rollbar-url], [Cadre][cadre-url], [Load Impact][load-impact-url], [Kloudless][kloudless-url], [Auklet][auklet-url], and [Lights On Software][lightson-url].
---
@ -55,7 +56,11 @@ There is a live example API for testing purposes, [available here][sandbox].
# Requirements
* Python (3.4, 3.5, 3.6, 3.7)
* Django (1.11, 2.0, 2.1)
* Django (1.11, 2.0, 2.1, 2.2)
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.
# Installation
@ -198,6 +203,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
[load-impact-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/load-impact-readme.png
[kloudless-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/kloudless-readme.png
[auklet-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/auklet-readme.png
[lightson-img]: https://raw.githubusercontent.com/encode/django-rest-framework/master/docs/img/premium/lightson-readme.png
[rover-url]: http://jobs.rover.com/
[sentry-url]: https://getsentry.com/welcome/
@ -207,6 +213,7 @@ Send a description of the issue via email to [rest-framework-security@googlegrou
[load-impact-url]: https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf
[kloudless-url]: https://hubs.ly/H0f30Lf0
[auklet-url]: https://auklet.io/
[lightson-url]: https://lightsonsoftware.com
[oauth1-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-rest-framework-oauth
[oauth2-section]: https://www.django-rest-framework.org/api-guide/authentication/#django-oauth-toolkit

View File

@ -137,7 +137,7 @@ You'll also need to create tokens for your users.
from rest_framework.authtoken.models import Token
token = Token.objects.create(user=...)
print token.key
print(token.key)
For clients to authenticate, the token key should be included in the `Authorization` HTTP header. The key should be prefixed by the string literal "Token", with whitespace separating the two strings. For example:
@ -391,10 +391,6 @@ Install the package using `pip`.
For details on configuration and usage see the Django REST framework OAuth documentation for [authentication][django-rest-framework-oauth-authentication] and [permissions][django-rest-framework-oauth-permissions].
## Digest Authentication
HTTP digest authentication is a widely implemented scheme that was intended to replace HTTP basic authentication, and which provides a simple encrypted authentication mechanism. [Juan Riaza][juanriaza] maintains the [djangorestframework-digestauth][djangorestframework-digestauth] package which provides HTTP digest authentication support for REST framework.
## JSON Web Token Authentication
JSON Web Token is a fairly new standard which can be used for token-based authentication. Unlike the built-in TokenAuthentication scheme, JWT Authentication doesn't need to use a database to validate a token. A package for JWT authentication is [djangorestframework-simplejwt][djangorestframework-simplejwt] which provides some features as well as a pluggable token blacklist app.

View File

@ -13,7 +13,7 @@ provided in Django.
Django provides a [`method_decorator`][decorator] to use
decorators with class based views. This can be used with
with other cache decorators such as [`cache_page`][page] and
other cache decorators such as [`cache_page`][page] and
[`vary_on_cookie`][cookie].
```python

View File

@ -124,7 +124,14 @@ A boolean representation.
When using HTML encoded form input be aware that omitting a value will always be treated as setting a field to `False`, even if it has a `default=True` option specified. This is because HTML checkbox inputs represent the unchecked state by omitting the value, so REST framework treats omission as if it is an empty checkbox input.
Note that default `BooleanField` instances will be generated with a `required=False` option (since Django `models.BooleanField` is always `blank=True`). If you want to change this behaviour explicitly declare the `BooleanField` on the serializer class.
Note that Django 2.1 removed the `blank` kwarg from `models.BooleanField`.
Prior to Django 2.1 `models.BooleanField` fields were always `blank=True`. Thus
since Django 2.1 default `serializers.BooleanField` instances will be generated
without the `required` kwarg (i.e. equivalent to `required=True`) whereas with
previous versions of Django, default `BooleanField` instances will be generated
with a `required=False` option. If you want to control this behaviour manually,
explicitly declare the `BooleanField` on the serializer class, or use the
`extra_kwargs` option to set the `required` flag.
Corresponds to `django.db.models.fields.BooleanField`.

View File

@ -127,7 +127,7 @@ Note that you can use both an overridden `.get_queryset()` and generic filtering
"""
model = Product
serializer_class = ProductSerializer
filter_class = ProductFilter
filterset_class = ProductFilter
def get_queryset(self):
user = self.request.user
@ -160,13 +160,13 @@ Or add the filter backend to an individual View or ViewSet.
...
filter_backends = (DjangoFilterBackend,)
If all you need is simple equality-based filtering, you can set a `filter_fields` attribute on the view, or viewset, listing the set of fields you wish to filter against.
If all you need is simple equality-based filtering, you can set a `filterset_fields` attribute on the view, or viewset, listing the set of fields you wish to filter against.
class ProductList(generics.ListAPIView):
queryset = Product.objects.all()
serializer_class = ProductSerializer
filter_backends = (DjangoFilterBackend,)
filter_fields = ('category', 'in_stock')
filterset_fields = ('category', 'in_stock')
This will automatically create a `FilterSet` class for the given fields, and will allow you to make requests such as:
@ -188,7 +188,7 @@ When in use, the browsable API will include a `SearchFilter` control:
The `SearchFilter` class will only be applied if the view has a `search_fields` attribute set. The `search_fields` attribute should be a list of names of text type fields on the model, such as `CharField` or `TextField`.
from rest_framework import filters
class UserListView(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
@ -218,6 +218,13 @@ For example:
By default, the search parameter is named `'search`', but this may be overridden with the `SEARCH_PARAM` setting.
To dynamically change search fields based on request content, it's possible to subclass the `SearchFilter` and override the `get_search_fields()` function. For example, the following subclass will only search on `title` if the query parameter `title_only` is in the request:
class CustomSearchFilter(self, view, request):
if request.query_params.get('title_only'):
return ('title',)
return super(CustomSearchFilter, self).get_search_fields(view, request)
For more details, see the [Django documentation][search-django-admin].
---
@ -298,9 +305,9 @@ A complete example using both `DjangoObjectPermissionsFilter` and `DjangoObjectP
**permissions.py**:
class CustomObjectPermissions(permissions.DjangoObjectPermissions):
"""
Similar to `DjangoObjectPermissions`, but adding 'view' permissions.
"""
"""
Similar to `DjangoObjectPermissions`, but adding 'view' permissions.
"""
perms_map = {
'GET': ['%(app_label)s.view_%(model_name)s'],
'OPTIONS': ['%(app_label)s.view_%(model_name)s'],
@ -314,11 +321,11 @@ A complete example using both `DjangoObjectPermissionsFilter` and `DjangoObjectP
**views.py**:
class EventViewSet(viewsets.ModelViewSet):
"""
Viewset that only lists events if user has 'view' permissions, and only
allows operations on individual events if user has appropriate 'view', 'add',
'change' or 'delete' permissions.
"""
"""
Viewset that only lists events if user has 'view' permissions, and only
allows operations on individual events if user has appropriate 'view', 'add',
'change' or 'delete' permissions.
"""
queryset = Event.objects.all()
serializer_class = EventSerializer
filter_backends = (filters.DjangoObjectPermissionsFilter,)

View File

@ -311,7 +311,7 @@ The [`drf-proxy-pagination` package][drf-proxy-pagination] includes a `ProxyPagi
## link-header-pagination
The [`django-rest-framework-link-header-pagination` package][drf-link-header-pagination] includes a `LinkHeaderPagination` class which provides pagination via an HTTP `Link` header as desribed in [Github's developer documentation](github-link-pagination).
The [`django-rest-framework-link-header-pagination` package][drf-link-header-pagination] includes a `LinkHeaderPagination` class which provides pagination via an HTTP `Link` header as described in [Github's developer documentation](github-link-pagination).
[cite]: https://docs.djangoproject.com/en/stable/topics/pagination/
[link-header]: ../img/link-header-pagination.png

View File

@ -102,7 +102,7 @@ If it is called without a `filename` URL keyword argument, then the client must
##### Notes:
* The `FileUploadParser` is for usage with native clients that can upload the file as a raw data request. For web-based uploads, or for native clients with multipart upload support, you should use the `MultiPartParser` parser instead.
* The `FileUploadParser` is for usage with native clients that can upload the file as a raw data request. For web-based uploads, or for native clients with multipart upload support, you should use the `MultiPartParser` instead.
* Since this parser's `media_type` matches any content type, `FileUploadParser` should generally be the only parser set on an API view.
* `FileUploadParser` respects Django's standard `FILE_UPLOAD_HANDLERS` setting, and the `request.upload_handlers` attribute. See the [Django documentation][upload-handlers] for more details.

View File

@ -10,9 +10,9 @@ Together with [authentication] and [throttling], permissions determine whether a
Permission checks are always run at the very start of the view, before any other code is allowed to proceed. Permission checks will typically use the authentication information in the `request.user` and `request.auth` properties to determine if the incoming request should be permitted.
Permissions are used to grant or deny access different classes of users to different parts of the API.
Permissions are used to grant or deny access for different classes of users to different parts of the API.
The simplest style of permission would be to allow access to any authenticated user, and deny access to any unauthenticated user. This corresponds the `IsAuthenticated` class in REST framework.
The simplest style of permission would be to allow access to any authenticated user, and deny access to any unauthenticated user. This corresponds to the `IsAuthenticated` class in REST framework.
A slightly less strict style of permission would be to allow full access to authenticated users, but allow read-only access to unauthenticated users. This corresponds to the `IsAuthenticatedOrReadOnly` class in REST framework.
@ -48,6 +48,19 @@ For example:
self.check_object_permissions(self.request, obj)
return obj
---
**Note**: With the exception of `DjangoObjectPermissions`, the provided
permission classes in `rest_framework.permssions` **do not** implement the
methods necessary to check object permissions.
If you wish to use the provided permission classes in order to check object
permissions, **you must** subclass them and implement the
`has_object_permission()` method described in the [_Custom
permissions_](#custom-permissions) section (below).
---
#### Limitations of object level permissions
For performance reasons the generic views will not automatically apply object level permissions to each instance in a queryset when returning a list of objects.
@ -104,7 +117,7 @@ __Note:__ when you set new permission classes through class attribute or decorat
Provided they inherit from `rest_framework.permissions.BasePermission`, permissions can be composed using standard Python bitwise operators. For example, `IsAuthenticatedOrReadOnly` could be written:
from rest_framework.permissions import BasePermission, IsAuthenticated
from rest_framework.permissions import BasePermission, IsAuthenticated, SAFE_METHODS
from rest_framework.response import Response
from rest_framework.views import APIView
@ -113,7 +126,7 @@ Provided they inherit from `rest_framework.permissions.BasePermission`, permissi
return request.method in SAFE_METHODS
class ExampleView(APIView):
permission_classes = (IsAuthenticated|ReadOnly)
permission_classes = (IsAuthenticated|ReadOnly,)
def get(self, request, format=None):
content = {
@ -121,7 +134,7 @@ Provided they inherit from `rest_framework.permissions.BasePermission`, permissi
}
return Response(content)
__Note:__ it only supports & -and- and | -or-.
__Note:__ it supports & (and), | (or) and ~ (not).
---
@ -274,7 +287,7 @@ The [Composed Permissions][composed-permissions] package provides a simple way t
## REST Condition
The [REST Condition][rest-condition] package is another extension for building complex permissions in a simple and convenient way. The extension allows you to combine permissions with logical operators.
The [REST Condition][rest-condition] package is another extension for building complex permissions in a simple and convenient way. The extension allows you to combine permissions with logical operators.
## DRY Rest Permissions
@ -284,9 +297,9 @@ The [DRY Rest Permissions][dry-rest-permissions] package provides the ability to
The [Django Rest Framework Roles][django-rest-framework-roles] package makes it easier to parameterize your API over multiple types of users.
## Django Rest Framework API Key
## Django REST Framework API Key
The [Django Rest Framework API Key][django-rest-framework-api-key] package allows you to ensure that every request made to the server requires an API key header. You can generate one from the django admin interface.
The [Django REST Framework API Key][djangorestframework-api-key] package provides the ability to authorize clients based on customizable API key headers. This package is targeted at situations in which regular user-based authentication (e.g. `TokenAuthentication`) is not suitable, e.g. allowing non-human clients to safely use your API. API keys are generated and validated through cryptographic methods and can be created and revoked from the Django admin interface at anytime.
## Django Rest Framework Role Filters
@ -304,6 +317,6 @@ The [Django Rest Framework Role Filters][django-rest-framework-role-filters] pac
[rest-condition]: https://github.com/caxap/rest_condition
[dry-rest-permissions]: https://github.com/Helioscene/dry-rest-permissions
[django-rest-framework-roles]: https://github.com/computer-lab/django-rest-framework-roles
[django-rest-framework-api-key]: https://github.com/manosim/django-rest-framework-api-key
[djangorestframework-api-key]: https://github.com/florimondmanca/djangorestframework-api-key
[django-rest-framework-role-filters]: https://github.com/allisson/django-rest-framework-role-filters
[django-rest-framework-guardian]: https://github.com/rpkilby/django-rest-framework-guardian

View File

@ -2,11 +2,9 @@ source: relations.py
# Serializer relations
> Bad programmers worry about the code.
> Good programmers worry about data structures and their relationships.
> Data structures, not algorithms, are central to programming.
>
> — [Linus Torvalds][cite]
> — [Rob Pike][cite]
Relational fields are used to represent model relationships. They can be applied to `ForeignKey`, `ManyToManyField` and `OneToOneField` relationships, as well as to reverse relationships, and custom relationships such as `GenericForeignKey`.
@ -24,7 +22,7 @@ To do so, open the Django shell, using `python manage.py shell`, then import the
>>> from myapp.serializers import AccountSerializer
>>> serializer = AccountSerializer()
>>> print repr(serializer) # Or `print(repr(serializer))` in Python 3.x.
>>> print(repr(serializer))
AccountSerializer():
id = IntegerField(label='ID', read_only=True)
name = CharField(allow_blank=True, max_length=100, required=False)
@ -48,12 +46,12 @@ In order to explain the various types of relational fields, we'll use a couple o
unique_together = ('album', 'order')
ordering = ['order']
def __unicode__(self):
def __str__(self):
return '%d: %s' % (self.order, self.title)
## StringRelatedField
`StringRelatedField` may be used to represent the target of the relationship using its `__unicode__` method.
`StringRelatedField` may be used to represent the target of the relationship using its `__str__` method.
For example, the following serializer.
@ -512,7 +510,7 @@ For example, given the following model for a tag, which has a generic relationsh
object_id = models.PositiveIntegerField()
tagged_object = GenericForeignKey('content_type', 'object_id')
def __unicode__(self):
def __str__(self):
return self.tag_name
And the following two models, which may have associated tags:
@ -592,7 +590,7 @@ 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]: https://lwn.net/Articles/193245/
[cite]: http://users.ece.utexas.edu/~adnan/pike.html
[reverse-relationships]: https://docs.djangoproject.com/en/stable/topics/db/queries/#following-relationships-backward
[routers]: https://www.django-rest-framework.org/api-guide/routers#defaultrouter
[generic-relations]: https://docs.djangoproject.com/en/stable/ref/contrib/contenttypes/#id1

View File

@ -89,7 +89,7 @@ The default JSON encoding style can be altered using the `UNICODE_JSON` and `COM
**.media_type**: `application/json`
**.format**: `'.json'`
**.format**: `'json'`
**.charset**: `None`
@ -127,7 +127,7 @@ See the [_HTML & Forms_ Topic Page][html-and-forms] for further examples of `Tem
**.media_type**: `text/html`
**.format**: `'.html'`
**.format**: `'html'`
**.charset**: `utf-8`
@ -149,7 +149,7 @@ You can use `StaticHTMLRenderer` either to return regular HTML pages using REST
**.media_type**: `text/html`
**.format**: `'.html'`
**.format**: `'html'`
**.charset**: `utf-8`
@ -165,7 +165,7 @@ This renderer will determine which other renderer would have been given highest
**.media_type**: `text/html`
**.format**: `'.api'`
**.format**: `'api'`
**.charset**: `utf-8`
@ -200,7 +200,7 @@ Note that views that have nested or list serializers for their input won't work
**.media_type**: `text/html`
**.format**: `'.admin'`
**.format**: `'admin'`
**.charset**: `utf-8`
@ -224,7 +224,7 @@ For more information see the [HTML & Forms][html-and-forms] documentation.
**.media_type**: `text/html`
**.format**: `'.form'`
**.format**: `'form'`
**.charset**: `utf-8`
@ -236,7 +236,7 @@ This renderer is used for rendering HTML multipart form data. **It is not suita
**.media_type**: `multipart/form-data; boundary=BoUnDaRyStRiNg`
**.format**: `'.multipart'`
**.format**: `'multipart'`
**.charset**: `utf-8`

View File

@ -50,7 +50,7 @@ The request exposes some properties that allow you to determine the result of th
## .accepted_renderer
The renderer instance what was selected by the content negotiation stage.
The renderer instance that was selected by the content negotiation stage.
## .accepted_media_type

View File

@ -112,8 +112,8 @@ has to be rendered into the actual bytes that are used in the response.
REST framework includes a few different renderers that you can use for
encoding the API schema.
* `renderers.OpenAPIRenderer` - Renders into YAML-based [OpenAPI][openapi], the most widely used API schema format.
* `renderers.JSONOpenAPIRenderer` - Renders into JSON-based [OpenAPI][openapi].
* `renderers.OpenAPIRenderer` - Renders into YAML-based [OpenAPI][open-api], the most widely used API schema format.
* `renderers.JSONOpenAPIRenderer` - Renders into JSON-based [OpenAPI][open-api].
* `renderers.CoreJSONRenderer` - Renders into [Core JSON][corejson], a format designed for
use with the `coreapi` client library.
@ -827,19 +827,11 @@ A short description of the meaning and intended usage of the input field.
[drf-yasg][drf-yasg] generates [OpenAPI][open-api] documents suitable for code generation - nested schemas,
named models, response bodies, enum/pattern/min/max validators, form parameters, etc.
## DRF OpenAPI
[DRF OpenAPI][drf-openapi] renders the schema generated by Django Rest Framework
in [OpenAPI][open-api] format.
[cite]: https://blog.heroku.com/archives/2014/1/8/json_schema_for_heroku_platform_api
[coreapi]: https://www.coreapi.org/
[corejson]: https://www.coreapi.org/specification/encoding/#core-json-encoding
[drf-yasg]: https://github.com/axnsan12/drf-yasg/
[open-api]: https://openapis.org/
[drf-openapi]: https://github.com/limdauto/drf_openapi
[json-hyperschema]: https://json-schema.org/latest/json-schema-hypermedia.html
[api-blueprint]: https://apiblueprint.org/
[static-files]: https://docs.djangoproject.com/en/stable/howto/static-files/

View File

@ -152,7 +152,7 @@ When deserializing data, you always need to call `is_valid()` before attempting
serializer.is_valid()
# False
serializer.errors
# {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']}
# {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}
Each key in the dictionary will be the field name, and the values will be lists of strings of any error messages corresponding to that field. The `non_field_errors` key may also be present, and will list any general validation errors. The name of the `non_field_errors` key may be customized using the `NON_FIELD_ERRORS_KEY` REST framework setting.
@ -208,7 +208,7 @@ To do any other validation that requires access to multiple fields, add a method
def validate(self, data):
"""
Check that the start is before the stop.
Check that start is before finish.
"""
if data['start'] > data['finish']:
raise serializers.ValidationError("finish must occur after start")
@ -253,7 +253,7 @@ When passing data to a serializer instance, the unmodified data will be made ava
By default, serializers must be passed values for all required fields or they will raise validation errors. You can use the `partial` argument in order to allow partial updates.
# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True)
serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)
## Dealing with nested objects
@ -293,7 +293,7 @@ When dealing with nested representations that support deserializing the data, an
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': [u'Enter a valid e-mail address.']}, 'created': [u'This field is required.']}
# {'user': {'email': ['Enter a valid e-mail address.']}, 'created': ['This field is required.']}
Similarly, the `.validated_data` property will include nested data structures.
@ -415,7 +415,7 @@ You can provide arbitrary additional context by passing a `context` argument whe
serializer = AccountSerializer(account, context={'request': request})
serializer.data
# {'id': 6, 'owner': u'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}
# {'id': 6, 'owner': 'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}
The context dictionary can be used within any serializer field logic, such as a custom `.to_representation()` method, by accessing the `self.context` attribute.
@ -1094,10 +1094,10 @@ This would then allow you to do the following:
>>> model = User
>>> fields = ('id', 'username', 'email')
>>>
>>> print UserSerializer(user)
>>> print(UserSerializer(user))
{'id': 2, 'username': 'jonwatts', 'email': 'jon@example.com'}
>>>
>>> print UserSerializer(user, fields=('id', 'email'))
>>> print(UserSerializer(user, fields=('id', 'email')))
{'id': 2, 'email': 'jon@example.com'}
## Customizing the default fields

View File

@ -209,7 +209,7 @@ directly.
Note that the requests client requires you to pass fully qualified URLs.
## `RequestsClient` and working with the database
## RequestsClient and working with the database
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.

View File

@ -82,8 +82,10 @@ The throttle classes provided by REST framework use Django's cache backend. You
If you need to use a cache other than `'default'`, you can do so by creating a custom throttle class and setting the `cache` attribute. For example:
from django.core.cache import caches
class CustomAnonRateThrottle(AnonRateThrottle):
cache = get_cache('alternate')
cache = caches['alternate']
You'll need to remember to also set your custom throttle class in the `'DEFAULT_THROTTLE_CLASSES'` settings key, or using the `throttle_classes` view attribute.

View File

@ -389,7 +389,7 @@ You can include `expiry_date` as a field option on a `ModelSerializer` class.
These fields will be mapped to `serializers.ReadOnlyField()` instances.
>>> serializer = InvitationSerializer()
>>> print repr(serializer)
>>> print(repr(serializer))
InvitationSerializer():
to_email = EmailField(max_length=75)
message = CharField(max_length=1000)
@ -960,6 +960,6 @@ The 3.2 release is planned to introduce an alternative admin-style interface to
You can follow development on the GitHub site, where we use [milestones to indicate planning timescales](https://github.com/encode/django-rest-framework/milestones).
[kickstarter]: https://www.kickstarter.com/projects/tomchristie/django-rest-framework-3
[sponsors]: https://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors
[sponsors]: https://www.django-rest-framework.org/community/kickstarter-announcement/#sponsors
[mixins.py]: https://github.com/encode/django-rest-framework/blob/master/rest_framework/mixins.py
[django-localization]: https://docs.djangoproject.com/en/stable/topics/i18n/translation/#localization-how-to-create-language-files

View File

@ -30,7 +30,7 @@ Note that as a result of this work a number of settings keys and generic view at
Until now, there has only been a single built-in pagination style in REST framework. We now have page, limit/offset and cursor based schemes included by default.
The cursor based pagination scheme is particularly smart, and is a better approach for clients iterating through large or frequently changing result sets. The scheme supports paging against non-unique indexes, by using both cursor and limit/offset information. It also allows for both forward and reverse cursor pagination. Much credit goes to David Cramer for [this blog post](http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api) on the subject.
The cursor based pagination scheme is particularly smart, and is a better approach for clients iterating through large or frequently changing result sets. The scheme supports paging against non-unique indexes, by using both cursor and limit/offset information. It also allows for both forward and reverse cursor pagination. Much credit goes to David Cramer for [this blog post](https://cra.mr/2011/03/08/building-cursors-for-the-disqus-api) on the subject.
#### Pagination controls in the browsable API.
@ -114,7 +114,7 @@ Note that the structure of the error responses is still the same. We still have
We include built-in translations both for standard exception cases, and for serializer validation errors.
The full list of supported languages can be found on our [Transifex project page](https://www.transifex.com/projects/p/django-rest-framework/).
The full list of supported languages can be found on our [Transifex project page](https://www.transifex.com/django-rest-framework-1/django-rest-framework/).
If you only wish to support a subset of the supported languages, use Django's standard `LANGUAGES` setting:
@ -155,14 +155,14 @@ We've now moved a number of packages out of the core of REST framework, and into
We're making this change in order to help distribute the maintenance workload, and keep better focus of the core essentials of the framework.
The change also means we can be more flexible with which external packages we recommend. For example, the excellently maintained [Django OAuth toolkit](https://github.com/evonove/django-oauth-toolkit) has now been promoted as our recommended option for integrating OAuth support.
The change also means we can be more flexible with which external packages we recommend. For example, the excellently maintained [Django OAuth toolkit](https://github.com/jazzband/django-oauth-toolkit) has now been promoted as our recommended option for integrating OAuth support.
The following packages are now moved out of core and should be separately installed:
* OAuth - [djangorestframework-oauth](https://jpadilla.github.io/django-rest-framework-oauth/)
* XML - [djangorestframework-xml](https://jpadilla.github.io/django-rest-framework-xml)
* YAML - [djangorestframework-yaml](https://jpadilla.github.io/django-rest-framework-yaml)
* JSONP - [djangorestframework-jsonp](https://jpadilla.github.io/django-rest-framework-jsonp)
* XML - [djangorestframework-xml](https://jpadilla.github.io/django-rest-framework-xml/)
* YAML - [djangorestframework-yaml](https://jpadilla.github.io/django-rest-framework-yaml/)
* JSONP - [djangorestframework-jsonp](https://jpadilla.github.io/django-rest-framework-jsonp/)
It's worth reiterating that this change in policy shouldn't mean any work in your codebase other than adding a new requirement and modifying some import paths. For example to install XML rendering, you would now do:

View File

@ -10,7 +10,7 @@ We've also fixed a huge number of issues, and made numerous cleanups and improve
Over the course of the 3.1.x series we've [resolved nearly 600 tickets](https://github.com/encode/django-rest-framework/issues?utf8=%E2%9C%93&q=closed%3A%3E2015-03-05) on our GitHub issue tracker. This means we're currently running at a rate of **closing around 100 issues or pull requests per month**.
None of this would have been possible without the support of our wonderful Kickstarter backers. If you're looking for a job in Django development we'd strongly recommend taking [a look through our sponsors](https://www.django-rest-framework.org/topics/kickstarter-announcement/#sponsors) and finding out who's hiring.
None of this would have been possible without the support of our wonderful Kickstarter backers. If you're looking for a job in Django development we'd strongly recommend taking [a look through our sponsors](https://www.django-rest-framework.org/community/kickstarter-announcement/#sponsors) and finding out who's hiring.
## AdminRenderer

View File

@ -36,13 +36,13 @@ Right now we're over 60% of the way towards achieving that.
*Every single sign-up makes a significant impact.*
<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://www.rover.com/careers/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://sentry.io/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>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [awesome sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), and [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf).*
*Many thanks to all our [awesome sponsors][sponsors], and in particular to our premium backers, [Rover](https://www.rover.com/careers/), [Sentry](https://sentry.io/welcome/), and [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf).*
---

View File

@ -32,14 +32,14 @@ we strongly encourage you to invest in its continued development by
**[signing up for a paid&nbsp;plan][funding]**.
<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://www.rover.com/careers/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://sentry.io/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://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>
*Many thanks to all our [sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), and [Machinalis](https://www.machinalis.com/#services).*
*Many thanks to all our [sponsors][sponsors], and in particular to our premium backers, [Rover](https://www.rover.com/careers/), [Sentry](https://sentry.io/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), and [Machinalis](https://www.machinalis.com/#services).*
---

View File

@ -39,16 +39,16 @@ we strongly encourage you to invest in its continued development by
**[signing up for a paid&nbsp;plan][funding]**.
<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://www.rover.com/careers/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://sentry.io/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</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="https://hello.machinalis.co.uk/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://machinalis.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar.png)">Rollbar</a></li>
<li><a href="https://micropyramid.com/django-rest-framework-development-services/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/mp-text-logo.png)">MicroPyramid</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://hello.machinalis.co.uk/), [Rollbar](https://rollbar.com), and [MicroPyramid](https://micropyramid.com/django-rest-framework-development-services/).*
*Many thanks to all our [sponsors][sponsors], and in particular to our premium backers, [Rover](https://www.rover.com/careers/), [Sentry](https://sentry.io/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://machinalis.com/), [Rollbar](https://rollbar.com), and [MicroPyramid](https://micropyramid.com/django-rest-framework-development-services/).*
---

View File

@ -33,15 +33,15 @@ If you use REST framework commercially and would like to see this work continue,
**[signing up for a paid&nbsp;plan][funding]**.
<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://www.rover.com/careers/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://sentry.io/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</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="https://hello.machinalis.co.uk/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://machinalis.com/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/Machinalis130.png)">Machinalis</a></li>
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar.png)">Rollbar</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*As well as our release sponsor, we'd like to say thanks in particular our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://hello.machinalis.co.uk/), and [Rollbar](https://rollbar.com).*
*As well as our release sponsor, we'd like to say thanks in particular our premium backers, [Rover](https://www.rover.com/careers/), [Sentry](https://sentry.io/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://machinalis.com/), and [Rollbar](https://rollbar.com).*
---

View File

@ -30,7 +30,7 @@ If you use REST framework commercially and would like to see this work continue,
**[signing up for a paid&nbsp;plan][funding]**.
*We'd like to say thanks in particular our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://hello.machinalis.co.uk/), and [Rollbar](https://rollbar.com).*
*We'd like to say thanks in particular our premium backers, [Rover](https://www.rover.com/careers/), [Sentry](https://sentry.io/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Machinalis](https://machinalis.com/), and [Rollbar](https://rollbar.com).*
---

View File

@ -30,8 +30,8 @@ If you use REST framework commercially and would like to see this work continue,
<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://www.rover.com/careers/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rover_130x130.png)">Rover.com</a></li>
<li><a href="https://sentry.io/welcome/" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/sentry130.png)">Sentry</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="https://auklet.io" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/auklet-new.png)">Auklet</a></li>
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar2.png)">Rollbar</a></li>
@ -41,7 +41,7 @@ If you use REST framework commercially and would like to see this work continue,
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Auklet](https://auklet.io/), [Rollbar](https://rollbar.com), [Cadre](https://cadre.com), [Load Impact](https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf), and [Kloudless](https://hubs.ly/H0f30Lf0).*
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](https://www.rover.com/careers/), [Sentry](https://sentry.io/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Auklet](https://auklet.io/), [Rollbar](https://rollbar.com), [Cadre](https://cadre.com), [Load Impact](https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf), and [Kloudless](https://hubs.ly/H0f30Lf0).*
---
@ -168,7 +168,7 @@ Both `APIView.exclude_from_schema` and the `exclude_from_schema` argument to the
For `APIView` you should instead set a `schema = None` attribute on the view class.
For function based views the `@schema` decorator can be used to exclude the view from the schema, by using `@schema(None)`.
For function-based views the `@schema` decorator can be used to exclude the view from the schema, by using `@schema(None)`.
---
@ -179,7 +179,7 @@ There are a large number of minor fixes and improvements in this release. See th
## What's next
We're planning to iteratively working towards OpenAPI becoming the standard schema
We're planning to iteratively work towards OpenAPI becoming the standard schema
representation. This will mean that the `coreapi` dependency will gradually become
removed, and we'll instead generate the schema directly, rather than building
a CoreAPI `Document` object.
@ -200,7 +200,7 @@ with the possibility that some of this work will eventually [feed back into
Django](https://www.aeracode.org/2018/06/04/django-async-roadmap/).
There will be further work on the [Uvicorn](https://www.uvicorn.org/)
webserver, as well as lots of functionality planned for the [Starlette](https://www.starlette.io/)
web server, as well as lots of functionality planned for the [Starlette](https://www.starlette.io/)
web framework, which is building a foundational set of tooling for working with
ASGI.

View File

@ -123,10 +123,10 @@ REST framework continues to be open-source and permissively licensed, but we fir
## What funding has enabled so far
* The [3.4](https://www.django-rest-framework.org/topics/3.4-announcement/) and [3.5](https://www.django-rest-framework.org/topics/3.5-announcement/) releases, including schema generation for both Swagger and RAML, a Python client library, a Command Line client, and addressing of a large number of outstanding issues.
* The [3.6](https://www.django-rest-framework.org/topics/3.6-announcement/) release, including JavaScript client library, and API documentation, complete with auto-generated code samples.
* The [3.7 release](https://www.django-rest-framework.org/topics/3.7-announcement/), made possible due to our collaborative funding model, focuses on improvements to schema generation and the interactive API documentation.
* The recent [3.8 release](https://www.django-rest-framework.org/topics/3.8-announcement/).
* The [3.4](https://www.django-rest-framework.org/community/3.4-announcement/) and [3.5](https://www.django-rest-framework.org/community/3.5-announcement/) releases, including schema generation for both Swagger and RAML, a Python client library, a Command Line client, and addressing of a large number of outstanding issues.
* The [3.6](https://www.django-rest-framework.org/community/3.6-announcement/) release, including JavaScript client library, and API documentation, complete with auto-generated code samples.
* The [3.7 release](https://www.django-rest-framework.org/community/3.7-announcement/), made possible due to our collaborative funding model, focuses on improvements to schema generation and the interactive API documentation.
* The recent [3.8 release](https://www.django-rest-framework.org/community/3.8-announcement/).
* Tom Christie, the creator of Django REST framework, working on the project full-time.
* Around 80-90 issues and pull requests closed per month since Tom Christie started working on the project full-time.
* A community & operations manager position part-time for 4 months, helping mature the business and grow sponsorship.
@ -341,7 +341,7 @@ For further enquires please contact <a href=mailto:funding@django-rest-framework
## Accountability
In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](https://www.encode.io/reports/march-2018) and regularly include financial reports and cost breakdowns.
In an effort to keep the project as transparent as possible, we are releasing [monthly progress reports](https://www.encode.io/reports/march-2018/) and regularly include financial reports and cost breakdowns.
<!-- Begin MailChimp Signup Form -->
<link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">

View File

@ -9,14 +9,12 @@ Looking for a new Django REST Framework related role? On this site we provide a
* [https://www.python.org/jobs/][python-org-jobs]
* [https://djangogigs.com][django-gigs-com]
* [https://djangojobs.net/jobs/][django-jobs-net]
* [http://djangojobbers.com][django-jobbers-com]
* [https://www.indeed.com/q-Django-jobs.html][indeed-com]
* [https://stackoverflow.com/jobs/developer-jobs-using-django][stackoverflow-com]
* [https://www.upwork.com/o/jobs/browse/skill/django-framework/][upwork-com]
* [https://www.technojobs.co.uk/django-jobs][technobjobs-co-uk]
* [https://remoteok.io/remote-django-jobs][remoteok-io]
* [https://www.remotepython.com/jobs/][remotepython-com]
* [https://weworkcontract.com/python-contract-jobs][weworkcontract-com]
Know of any other great resources for Django REST Framework jobs that are missing in our list? Please [submit a pull request][submit-pr] or [email us][anna-email].
@ -28,14 +26,12 @@ Wonder how else you can help? One of the best ways you can help Django REST Fram
[python-org-jobs]: https://www.python.org/jobs/
[django-gigs-com]: https://djangogigs.com
[django-jobs-net]: https://djangojobs.net/jobs/
[django-jobbers-com]: http://djangojobbers.com
[indeed-com]: https://www.indeed.com/q-Django-jobs.html
[stackoverflow-com]: https://stackoverflow.com/jobs/developer-jobs-using-django
[upwork-com]: https://www.upwork.com/o/jobs/browse/skill/django-framework/
[technobjobs-co-uk]: https://www.technojobs.co.uk/django-jobs
[remoteok-io]: https://remoteok.io/remote-django-jobs
[remotepython-com]: https://www.remotepython.com/jobs/
[weworkcontract-com]: https://weworkcontract.com/python-contract-jobs
[drf-funding]: https://fund.django-rest-framework.org/topics/funding/
[submit-pr]: https://github.com/encode/django-rest-framework
[anna-email]: mailto:anna@django-rest-framework.org

View File

@ -47,8 +47,8 @@ Our platinum sponsors have each made a hugely substantial contribution to the fu
</ul>
<ul class="sponsor platinum">
<li><a href="https://www.divio.ch/" rel="nofollow" style="background-image:url(../../img/sponsors/1-divio.png);">Divio</a></li>
<li><a href="http://company.onlulu.com/en/" rel="nofollow" style="background-image:url(../../img/sponsors/1-lulu.png);">Lulu</a></li>
<li><a href="https://www.divio.com/" rel="nofollow" style="background-image:url(../../img/sponsors/1-divio.png);">Divio</a></li>
<li><a href="https://onlulu.com" rel="nofollow" style="background-image:url(../../img/sponsors/1-lulu.png);">Lulu</a></li>
<li><a href="https://p.ota.to/" rel="nofollow" style="background-image:url(../../img/sponsors/1-potato.png);">Potato</a></li>
<li><a href="http://www.wiredrive.com/" rel="nofollow" style="background-image:url(../../img/sponsors/1-wiredrive.png);">Wiredrive</a></li>
<li><a href="http://www.cyaninc.com/" rel="nofollow" style="background-image:url(../../img/sponsors/1-cyan.png);">Cyan</a></li>
@ -81,8 +81,8 @@ Our gold sponsors include companies large and small. Many thanks for their signi
<li><a href="https://www.lightningkite.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-lightning_kite.png);">Lightning Kite</a></li>
<li><a href="https://opbeat.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-opbeat.png);">Opbeat</a></li>
<li><a href="https://koordinates.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-koordinates.png);">Koordinates</a></li>
<li><a href="http://pulsecode.ca" rel="nofollow" style="background-image:url(../../img/sponsors/2-pulsecode.png);">Pulsecode Inc.</a></li>
<li><a href="http://singinghorsestudio.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-singing-horse.png);">Singing Horse Studio Ltd.</a></li>
<li><a rel="nofollow" style="background-image:url(../../img/sponsors/2-pulsecode.png);">Pulsecode Inc.</a></li>
<li><a rel="nofollow" style="background-image:url(../../img/sponsors/2-singing-horse.png);">Singing Horse Studio Ltd.</a></li>
<li><a href="https://www.heroku.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-heroku.png);">Heroku</a></li>
<li><a href="https://www.rheinwerk-verlag.de/" rel="nofollow" style="background-image:url(../../img/sponsors/2-rheinwerk_verlag.png);">Rheinwerk Verlag</a></li>
<li><a href="https://www.securitycompass.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-security_compass.png);">Security Compass</a></li>
@ -90,9 +90,9 @@ Our gold sponsors include companies large and small. Many thanks for their signi
<li><a href="http://www.hipflaskapp.com" rel="nofollow" style="background-image:url(../../img/sponsors/2-hipflask.png);">Hipflask</a></li>
<li><a href="http://www.crate.io/" rel="nofollow" style="background-image:url(../../img/sponsors/2-crate.png);">Crate</a></li>
<li><a href="http://crypticocorp.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-cryptico.png);">Cryptico Corp</a></li>
<li><a href="http://www.nexthub.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-nexthub.png);">NextHub</a></li>
<li><a rel="nofollow" style="background-image:url(../../img/sponsors/2-nexthub.png);">NextHub</a></li>
<li><a href="https://www.compile.com/" rel="nofollow" style="background-image:url(../../img/sponsors/2-compile.png);">Compile</a></li>
<li><a href="http://wusawork.org" rel="nofollow" style="background-image:url(../../img/sponsors/2-wusawork.png);">WusaWork</a></li>
<li><a rel="nofollow" style="background-image:url(../../img/sponsors/2-wusawork.png);">WusaWork</a></li>
<li><a href="http://envisionlinux.org/blog" rel="nofollow">Envision Linux</a></li>
</ul>

View File

@ -18,9 +18,9 @@ REST framework releases follow a formal deprecation policy, which is in line wit
The timeline for deprecation of a feature present in version 1.0 would work as follows:
* Version 1.1 would remain **fully backwards compatible** with 1.0, but would raise `PendingDeprecationWarning` warnings if you use the feature that are due to be deprecated. These warnings are **silent by default**, but can be explicitly enabled when you're ready to start migrating any required changes. For example if you start running your tests using `python -Wd manage.py test`, you'll be warned of any API changes you need to make.
* Version 1.1 would remain **fully backwards compatible** with 1.0, but would raise `RemovedInDRF13Warning` warnings, subclassing `PendingDeprecationWarning`, if you use the feature that are due to be deprecated. These warnings are **silent by default**, but can be explicitly enabled when you're ready to start migrating any required changes. For example if you start running your tests using `python -Wd manage.py test`, you'll be warned of any API changes you need to make.
* Version 1.2 would escalate these warnings to `DeprecationWarning`, which is loud by default.
* Version 1.2 would escalate these warnings to subclass `DeprecationWarning`, which is loud by default.
* Version 1.3 would remove the deprecated bits of API entirely.
@ -40,9 +40,38 @@ You can determine your currently installed version using `pip show`:
## 3.9.x series
### 3.9.2
**Date**: [3rd March 2019][3.9.1-milestone]
* Routers: invalidate `_urls` cache on `register()` [#6407][gh6407]
* Deferred schema renderer creation to avoid requiring pyyaml. [#6416][gh6416]
* Added 'request_forms' block to base.html [#6340][gh6340]
* Fixed SchemaView to reset renderer on exception. [#6429][gh6429]
* Update Django Guardian dependency. [#6430][gh6430]
* Ensured support for Django 2.2 [#6422][gh6422] & [#6455][gh6455]
* Made templates compatible with session-based CSRF. [#6207][gh6207]
* Adjusted field `validators` to accept non-list iterables. [#6282][gh6282]
* Added SearchFilter.get_search_fields() hook. [#6279][gh6279]
* Fix DeprecationWarning when accessing collections.abc classes via collections [#6268][gh6268]
* Allowed Q objects in limit_choices_to introspection. [#6472][gh6472]
* Added lazy evaluation to composed permissions. [#6463][gh6463]
* Add negation ~ operator to permissions composition [#6361][gh6361]
* Avoided calling distinct on annotated fields in SearchFilter. [#6240][gh6240]
* Introduced `RemovedInDRF…Warning` classes to simplify deprecations. [#6480][gh6480]
### 3.9.1
**Date**: [16th Janurary 2019][3.9.1-milestone]
* Resolve XSS issue in browsable API. [#6330][gh6330]
* Upgrade Bootstrap to 3.4.0 to resolve XSS issue.
* Resolve issues with composable permissions. [#6299][gh6299]
* Respect `limit_choices_to` on foreign keys. [#6371][gh6371]
### 3.9.0
**Date**: [18st October 2018][3.9.0-milestone]
**Date**: [18th October 2018][3.9.0-milestone]
* Improvements to ViewSet extra actions [#5605][gh5605]
* Fix `action` support for ViewSet suffixes [#6081][gh6081]
@ -1135,6 +1164,8 @@ For older release notes, [please see the version 2.x documentation][old-release-
[3.8.1-milestone]: https://github.com/encode/django-rest-framework/milestone/67?closed=1
[3.8.2-milestone]: https://github.com/encode/django-rest-framework/milestone/68?closed=1
[3.9.0-milestone]: https://github.com/encode/django-rest-framework/milestone/66?closed=1
[3.9.1-milestone]: https://github.com/encode/django-rest-framework/milestone/70?closed=1
[3.9.1-milestone]: https://github.com/encode/django-rest-framework/milestone/71?closed=1
<!-- 3.0.1 -->
[gh2013]: https://github.com/encode/django-rest-framework/issues/2013
@ -2052,3 +2083,26 @@ For older release notes, [please see the version 2.x documentation][old-release-
[gh6233]: https://github.com/encode/django-rest-framework/issues/6233
[gh5753]: https://github.com/encode/django-rest-framework/issues/5753
[gh6229]: https://github.com/encode/django-rest-framework/issues/6229
<!-- 3.9.1 -->
[gh6330]: https://github.com/encode/django-rest-framework/issues/6330
[gh6299]: https://github.com/encode/django-rest-framework/issues/6299
[gh6371]: https://github.com/encode/django-rest-framework/issues/6371
<!-- 3.9.2 -->
[gh6480]: https://github.com/encode/django-rest-framework/issues/6480
[gh6240]: https://github.com/encode/django-rest-framework/issues/6240
[gh6361]: https://github.com/encode/django-rest-framework/issues/6361
[gh6463]: https://github.com/encode/django-rest-framework/issues/6463
[gh6472]: https://github.com/encode/django-rest-framework/issues/6472
[gh6268]: https://github.com/encode/django-rest-framework/issues/6268
[gh6279]: https://github.com/encode/django-rest-framework/issues/6279
[gh6282]: https://github.com/encode/django-rest-framework/issues/6282
[gh6207]: https://github.com/encode/django-rest-framework/issues/6207
[gh6455]: https://github.com/encode/django-rest-framework/issues/6455
[gh6422]: https://github.com/encode/django-rest-framework/issues/6422
[gh6430]: https://github.com/encode/django-rest-framework/issues/6430
[gh6429]: https://github.com/encode/django-rest-framework/issues/6429
[gh6340]: https://github.com/encode/django-rest-framework/issues/6340
[gh6416]: https://github.com/encode/django-rest-framework/issues/6416
[gh6407]: https://github.com/encode/django-rest-framework/issues/6407

View File

@ -183,8 +183,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque
* [djangorestframework-digestauth][djangorestframework-digestauth] - Provides Digest Access Authentication support.
* [django-oauth-toolkit][django-oauth-toolkit] - Provides OAuth 2.0 support.
* [djangorestframework-jwt][djangorestframework-jwt] - Provides JSON Web Token Authentication support.
* [djangorestframework-simplejwt][djangorestframework-simplejwt] - An alternative package that provides JSON Web Token Authentication support.
* [djangorestframework-simplejwt][djangorestframework-simplejwt] - Provides JSON Web Token Authentication support.
* [hawkrest][hawkrest] - Provides Hawk HTTP Authorization.
* [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.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -73,11 +73,12 @@ continued development by **[signing up for a paid plan][funding]**.
<li><a href="https://rollbar.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/rollbar2.png)">Rollbar</a></li>
<li><a href="https://cadre.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/cadre.png)">Cadre</a></li>
<li><a href="https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/load-impact.png)">Load Impact</a></li>
<li><a href="https://hubs.ly/H0f30Lf0" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/kloudless.png)">Kloudless</a></li>
<li><a href="https://hubs.ly/H0f30Lf0" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/kloudless-plus-text.png)">Kloudless</a></li>
<li><a href="https://lightsonsoftware.com" style="background-image: url(https://fund-rest-framework.s3.amazonaws.com/lightson-dark.png)">Lights On Software</a></li>
</ul>
<div style="clear: both; padding-bottom: 20px;"></div>
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Auklet](https://auklet.io/), [Rollbar](https://rollbar.com), [Cadre](https://cadre.com), [Load Impact](https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf), and [Kloudless](https://hubs.ly/H0f30Lf0).*
*Many thanks to all our [wonderful sponsors][sponsors], and in particular to our premium backers, [Rover](http://jobs.rover.com/), [Sentry](https://getsentry.com/welcome/), [Stream](https://getstream.io/?utm_source=drf&utm_medium=banner&utm_campaign=drf), [Auklet](https://auklet.io/), [Rollbar](https://rollbar.com), [Cadre](https://cadre.com), [Load Impact](https://loadimpact.com/?utm_campaign=Sponsorship%20links&utm_source=drf&utm_medium=drf), [Kloudless](https://hubs.ly/H0f30Lf0), and [Lights On Software](https://lightsonsoftware.com).*
---
@ -86,7 +87,10 @@ continued development by **[signing up for a paid plan][funding]**.
REST framework requires the following:
* Python (2.7, 3.4, 3.5, 3.6, 3.7)
* Django (1.11, 2.0, 2.1)
* Django (1.11, 2.0, 2.1, 2.2)
We **highly recommend** and only officially support the latest patch release of
each Python and Django series.
The following packages are optional:
@ -106,7 +110,7 @@ Install using `pip`, including any optional packages you want...
...or clone the project from github.
git clone git@github.com:encode/django-rest-framework.git
git clone https://github.com/encode/django-rest-framework
Add `'rest_framework'` to your `INSTALLED_APPS` setting.

View File

@ -94,6 +94,8 @@ To add branding and customize the look-and-feel of the login template, create a
You can add your site name or branding by including the branding block:
{% extends "rest_framework/login_base.html" %}
{% block branding %}
<h3 style="margin: 0 0 20px;">My Site Name</h3>
{% endblock %}

View File

@ -81,7 +81,7 @@ was later [dropped from the spec][html5]. There remains
as well as how to support content types other than form-encoded data.
[cite]: https://www.amazon.com/RESTful-Web-Services-Leonard-Richardson/dp/0596529260
[ajax-form]: https://github.com/encode/ajax-form
[ajax-form]: https://github.com/tomchristie/ajax-form
[rails]: https://guides.rubyonrails.org/form_helpers.html#how-do-forms-with-put-or-delete-methods-work
[html5]: https://www.w3.org/TR/html5-diff/#changes-2010-06-24
[put_delete]: http://amundsen.com/examples/put-delete-forms/

View File

@ -193,22 +193,6 @@ This also translates into a very useful interactive documentation viewer in the
![Screenshot - drf-yasg][image-drf-yasg]
#### DRF OpenAPI
[DRF OpenAPI][drf-openapi] bridges the gap between OpenAPI specification and tool chain with the schema exposed
out-of-the-box by Django Rest Framework. Its goals are:
* To be dropped into any existing DRF project without any code change necessary.
* Provide clear disctinction between request schema and response schema.
* Provide a versioning mechanism for each schema. Support defining schema by version range syntax, e.g. >1.0, <=2.0
* Support multiple response codes, not just 200
* All this information should be bound to view methods, not view classes.
It also tries to stay current with the maturing schema generation mechanism provided by DRF.
![Screenshot - DRF OpenAPI][image-drf-openapi]
---
#### DRF Docs
@ -338,8 +322,6 @@ To implement a hypermedia API you'll need to decide on an appropriate media type
[cite]: https://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
[drf-yasg]: https://github.com/axnsan12/drf-yasg/
[image-drf-yasg]: ../img/drf-yasg.png
[drf-openapi]: https://github.com/limdauto/drf_openapi/
[image-drf-openapi]: ../img/drf-openapi.png
[drfdocs-repo]: https://github.com/ekonstantinidis/django-rest-framework-docs
[drfdocs-website]: https://www.drfdocs.com/
[drfdocs-demo]: http://demo.drfdocs.com/

View File

@ -137,20 +137,20 @@ Okay, once we've got a few imports out of the way, let's create a couple of code
snippet = Snippet(code='foo = "bar"\n')
snippet.save()
snippet = Snippet(code='print "hello, world"\n')
snippet = Snippet(code='print("hello, world")\n')
snippet.save()
We've now got a few snippet instances to play with. Let's take a look at serializing one of those instances.
serializer = SnippetSerializer(snippet)
serializer.data
# {'id': 2, 'title': u'', 'code': u'print "hello, world"\n', 'linenos': False, 'language': u'python', 'style': u'friendly'}
# {'id': 2, 'title': '', 'code': 'print("hello, world")\n', 'linenos': False, 'language': 'python', 'style': 'friendly'}
At this point we've translated the model instance into Python native datatypes. To finalize the serialization process we render the data into `json`.
content = JSONRenderer().render(serializer.data)
content
# '{"id": 2, "title": "", "code": "print \\"hello, world\\"\\n", "linenos": false, "language": "python", "style": "friendly"}'
# '{"id": 2, "title": "", "code": "print(\\"hello, world\\")\\n", "linenos": false, "language": "python", "style": "friendly"}'
Deserialization is similar. First we parse a stream into Python native datatypes...
@ -165,7 +165,7 @@ Deserialization is similar. First we parse a stream into Python native datatype
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('title', ''), ('code', 'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
# OrderedDict([('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])
serializer.save()
# <Snippet: Snippet object>
@ -175,7 +175,7 @@ We can also serialize querysets instead of model instances. To do so we simply
serializer = SnippetSerializer(Snippet.objects.all(), many=True)
serializer.data
# [OrderedDict([('id', 1), ('title', u''), ('code', u'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', u''), ('code', u'print "hello, world"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', u''), ('code', u'print "hello, world"'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
# [OrderedDict([('id', 1), ('title', ''), ('code', 'foo = "bar"\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 2), ('title', ''), ('code', 'print("hello, world")\n'), ('linenos', False), ('language', 'python'), ('style', 'friendly')]), OrderedDict([('id', 3), ('title', ''), ('code', 'print("hello, world")'), ('linenos', False), ('language', 'python'), ('style', 'friendly')])]
## Using ModelSerializers
@ -338,7 +338,7 @@ Finally, we can get a list of all of the snippets:
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
@ -354,7 +354,7 @@ Or we can get a particular snippet by referencing its id:
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"

View File

@ -143,7 +143,7 @@ We can get a list of all of the snippets, as before.
{
"id": 2,
"title": "",
"code": "print \"hello, world\"\n",
"code": "print(\"hello, world\")\n",
"linenos": false,
"language": "python",
"style": "friendly"
@ -163,24 +163,24 @@ Or by appending a format suffix:
Similarly, we can control the format of the request that we send, using the `Content-Type` header.
# POST using form data
http --form POST http://127.0.0.1:8000/snippets/ code="print 123"
http --form POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"id": 3,
"title": "",
"code": "print 123",
"code": "print(123)",
"linenos": false,
"language": "python",
"style": "friendly"
}
# POST using JSON
http --json POST http://127.0.0.1:8000/snippets/ code="print 456"
http --json POST http://127.0.0.1:8000/snippets/ code="print(456)"
{
"id": 4,
"title": "",
"code": "print 456",
"code": "print(456)",
"linenos": false,
"language": "python",
"style": "friendly"

View File

@ -197,7 +197,7 @@ If we're interacting with the API programmatically we need to explicitly provide
If we try to create a snippet without authenticating, we'll get an error:
http POST http://127.0.0.1:8000/snippets/ code="print 123"
http POST http://127.0.0.1:8000/snippets/ code="print(123)"
{
"detail": "Authentication credentials were not provided."
@ -205,13 +205,13 @@ If we try to create a snippet without authenticating, we'll get an error:
We can make a successful request by including the username and password of one of the users we created earlier.
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
http -a admin:password123 POST http://127.0.0.1:8000/snippets/ code="print(789)"
{
"id": 1,
"owner": "admin",
"title": "foo",
"code": "print 789",
"code": "print(789)",
"linenos": false,
"language": "python",
"style": "friendly"

View File

@ -106,7 +106,7 @@ If we're going to have a hyperlinked API, we need to make sure we name our URL p
After adding all those names into our URLconf, our final `snippets/urls.py` file should look like this:
from django.conf.urls import url, include
from django.urls import path
from rest_framework.urlpatterns import format_suffix_patterns
from snippets import views

View File

@ -29,9 +29,10 @@ automatically generated schemas. Since we're using viewsets and routers,
we can simply use the automatic schema generation.
You'll need to install the `coreapi` python package in order to include an
API schema.
API schema, and `pyyaml` to render the schema into the commonly used
YAML-based OpenAPI format.
$ pip install coreapi
$ pip install coreapi pyyaml
We can now include a schema for our API, by including an autogenerated schema
view in our URL configuration.

View File

@ -46,7 +46,7 @@ The project layout should look like:
./tutorial/urls.py
./tutorial/wsgi.py
It may look unusual that the application has been created within the project directory. Using the project's namespace avoids name clashes with external module (topic goes outside the scope of the quickstart).
It may look unusual that the application has been created within the project directory. Using the project's namespace avoids name clashes with external modules (a topic that goes outside the scope of the quickstart).
Now sync your database for the first time:
@ -77,7 +77,7 @@ First up we're going to define some serializers. Let's create a new module named
model = Group
fields = ('url', 'name')
Notice that we're using hyperlinked relations in this case, with `HyperlinkedModelSerializer`. You can also use primary key and various other relationships, but hyperlinking is good RESTful design.
Notice that we're using hyperlinked relations in this case with `HyperlinkedModelSerializer`. You can also use primary key and various other relationships, but hyperlinking is good RESTful design.
## Views
@ -111,7 +111,7 @@ We can easily break these down into individual views if we need to, but using vi
Okay, now let's wire up the API URLs. On to `tutorial/urls.py`...
from django.conf.urls import url, include
from django.urls import include, path
from rest_framework import routers
from tutorial.quickstart import views
@ -122,8 +122,8 @@ Okay, now let's wire up the API URLs. On to `tutorial/urls.py`...
# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
path('', include(router.urls)),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]
Because we're using viewsets instead of views, we can automatically generate the URL conf for our API, by simply registering the viewsets with a router class.
@ -133,7 +133,7 @@ Again, if we need more control over the API URLs we can simply drop down to usin
Finally, we're including default login and logout views for use with the browsable API. That's optional, but useful if your API requires authentication and you want to use the browsable API.
## Pagination
Pagination allows you to control how many objects per page are returned. To enable it add following lines to the `tutorial/settings.py`
Pagination allows you to control how many objects per page are returned. To enable it add the following lines to `tutorial/settings.py`
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',

0
docs_theme/css/bootstrap-responsive.css vendored Executable file → Normal file
View File

0
docs_theme/css/bootstrap.css vendored Executable file → Normal file
View File

0
docs_theme/js/bootstrap-2.1.1-min.js vendored Executable file → Normal file
View File

View File

@ -1,7 +1,7 @@
# Optional packages which may be used with REST framework.
psycopg2-binary==2.7.5
markdown==2.6.11
django-guardian==1.4.9
django-guardian==1.5.0
django-filter==1.1.0
coreapi==2.3.1
coreschema==0.0.4

View File

@ -8,10 +8,10 @@ ______ _____ _____ _____ __
"""
__title__ = 'Django REST framework'
__version__ = '3.9.0'
__version__ = '3.9.2'
__author__ = 'Tom Christie'
__license__ = 'BSD 2-Clause'
__copyright__ = 'Copyright 2011-2018 Tom Christie'
__copyright__ = 'Copyright 2011-2019 Encode OSS Ltd'
# Version synonym
VERSION = __version__
@ -23,3 +23,11 @@ HTTP_HEADER_ENCODING = 'iso-8859-1'
ISO_8601 = 'iso-8601'
default_app_config = 'rest_framework.apps.RestFrameworkConfig'
class RemovedInDRF310Warning(DeprecationWarning):
pass
class RemovedInDRF311Warning(PendingDeprecationWarning):
pass

View File

@ -3,6 +3,8 @@ The `compat` module provides support for backwards compatibility with older
versions of Django/Python, and compatibility wrappers around optional packages.
"""
import sys
from django.conf import settings
from django.core import validators
from django.utils import six
@ -10,17 +12,10 @@ from django.views.generic import View
try:
# Python 3
from collections.abc import Mapping # noqa
from collections.abc import Mapping, MutableMapping # noqa
except ImportError:
# Python 2.7
from collections import Mapping # noqa
try:
# Python 3
import urllib.parse as urlparse # noqa
except ImportError:
# Python 2.7
from urlparse import urlparse # noqa
from collections import Mapping, MutableMapping # noqa
try:
from django.urls import ( # noqa
@ -39,6 +34,11 @@ try:
except ImportError:
ProhibitNullCharactersValidator = None
try:
from unittest import mock
except ImportError:
mock = None
def get_original_route(urlpattern):
"""
@ -166,6 +166,10 @@ def is_guardian_installed():
"""
django-guardian is optional and only imported if in INSTALLED_APPS.
"""
if six.PY2:
# Guardian 1.5.0, for Django 2.2 is NOT compatible with Python 2.7.
# Remove when dropping PY2.
return False
return 'guardian' in settings.INSTALLED_APPS
@ -315,3 +319,7 @@ class MinLengthValidator(CustomValidatorMessage, validators.MinLengthValidator):
class MaxLengthValidator(CustomValidatorMessage, validators.MaxLengthValidator):
pass
# Version Constants.
PY36 = sys.version_info >= (3, 6)

View File

@ -13,6 +13,7 @@ import warnings
from django.forms.utils import pretty_name
from django.utils import six
from rest_framework import RemovedInDRF310Warning
from rest_framework.views import APIView
@ -224,7 +225,7 @@ def detail_route(methods=None, **kwargs):
warnings.warn(
"`detail_route` is deprecated and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=True)` instead.",
DeprecationWarning, stacklevel=2
RemovedInDRF310Warning, stacklevel=2
)
def decorator(func):
@ -242,7 +243,7 @@ def list_route(methods=None, **kwargs):
warnings.warn(
"`list_route` is deprecated and will be removed in 3.10 in favor of "
"`action`, which accepts a `detail` bool. Use `@action(detail=False)` instead.",
DeprecationWarning, stacklevel=2
RemovedInDRF310Warning, stacklevel=2
)
def decorator(func):

View File

@ -1,4 +1,3 @@
import collections
import copy
import datetime
import decimal
@ -31,7 +30,7 @@ from pytz.exceptions import InvalidTimeError
from rest_framework import ISO_8601
from rest_framework.compat import (
MaxLengthValidator, MaxValueValidator, MinLengthValidator,
Mapping, MaxLengthValidator, MaxValueValidator, MinLengthValidator,
MinValueValidator, ProhibitNullCharactersValidator, unicode_repr,
unicode_to_repr
)
@ -94,7 +93,7 @@ def get_attribute(instance, attrs):
"""
for attr in attrs:
try:
if isinstance(instance, collections.Mapping):
if isinstance(instance, Mapping):
instance = instance[attr]
else:
instance = getattr(instance, attr)
@ -348,7 +347,7 @@ class Field(object):
self.default_empty_html = default
if validators is not None:
self.validators = validators[:]
self.validators = list(validators)
# These are set up by `.bind()` when the field is added to a serializer.
self.field_name = None
@ -408,7 +407,7 @@ class Field(object):
self._validators = validators
def get_validators(self):
return self.default_validators[:]
return list(self.default_validators)
def get_initial(self):
"""
@ -1659,7 +1658,7 @@ class ListField(Field):
"""
if html.is_html_input(data):
data = html.parse_html_list(data, default=[])
if isinstance(data, type('')) or isinstance(data, collections.Mapping) or not hasattr(data, '__iter__'):
if isinstance(data, (type(''), Mapping)) or not hasattr(data, '__iter__'):
self.fail('not_a_list', input_type=type(data).__name__)
if not self.allow_empty and len(data) == 0:
self.fail('empty')
@ -1723,9 +1722,6 @@ class DictField(Field):
return self.run_child_validation(data)
def to_representation(self, value):
"""
List of object instances -> List of dicts of primitive datatypes.
"""
return {
six.text_type(key): self.child.to_representation(val) if val is not None else None
for key, val in value.items()

View File

@ -16,6 +16,7 @@ from django.utils import six
from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _
from rest_framework import RemovedInDRF310Warning
from rest_framework.compat import (
coreapi, coreschema, distinct, is_guardian_installed
)
@ -52,6 +53,14 @@ class SearchFilter(BaseFilterBackend):
search_title = _('Search')
search_description = _('A search term.')
def get_search_fields(self, view, request):
"""
Search fields are obtained from the view, but the request is always
passed to this method. Sub-classes can override this method to
dynamically change the search fields based on request content.
"""
return getattr(view, 'search_fields', None)
def get_search_terms(self, request):
"""
Search terms are set by a ?search=... query parameter,
@ -76,6 +85,9 @@ class SearchFilter(BaseFilterBackend):
opts = queryset.model._meta
if search_field[0] in self.lookup_prefixes:
search_field = search_field[1:]
# Annotated fields do not need to be distinct
if isinstance(queryset, models.QuerySet) and search_field in queryset.query.annotations:
return False
parts = search_field.split(LOOKUP_SEP)
for part in parts:
field = opts.get_field(part)
@ -89,7 +101,7 @@ class SearchFilter(BaseFilterBackend):
return False
def filter_queryset(self, request, queryset, view):
search_fields = getattr(view, 'search_fields', None)
search_fields = self.get_search_fields(view, request)
search_terms = self.get_search_terms(request)
if not search_fields or not search_terms:
@ -287,7 +299,7 @@ class DjangoObjectPermissionsFilter(BaseFilterBackend):
warnings.warn(
"`DjangoObjectPermissionsFilter` has been deprecated and moved to "
"the 3rd-party django-rest-framework-guardian package.",
DeprecationWarning, stacklevel=2
RemovedInDRF310Warning, stacklevel=2
)
assert is_guardian_installed(), 'Using DjangoObjectPermissionsFilter, but django-guardian is not installed'

View File

@ -32,8 +32,10 @@ class Command(BaseCommand):
self.stdout.write(output.decode('utf-8'))
def get_renderer(self, format):
return {
'corejson': CoreJSONRenderer(),
'openapi': OpenAPIRenderer(),
'openapi-json': JSONOpenAPIRenderer()
renderer_cls = {
'corejson': CoreJSONRenderer,
'openapi': OpenAPIRenderer,
'openapi-json': JSONOpenAPIRenderer,
}[format]
return renderer_cls()

View File

@ -10,7 +10,34 @@ from rest_framework import exceptions
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')
class OperandHolder:
class OperationHolderMixin:
def __and__(self, other):
return OperandHolder(AND, self, other)
def __or__(self, other):
return OperandHolder(OR, self, other)
def __rand__(self, other):
return OperandHolder(AND, other, self)
def __ror__(self, other):
return OperandHolder(OR, other, self)
def __invert__(self):
return SingleOperandHolder(NOT, self)
class SingleOperandHolder(OperationHolderMixin):
def __init__(self, operator_class, op1_class):
self.operator_class = operator_class
self.op1_class = op1_class
def __call__(self, *args, **kwargs):
op1 = self.op1_class(*args, **kwargs)
return self.operator_class(op1)
class OperandHolder(OperationHolderMixin):
def __init__(self, operator_class, op1_class, op2_class):
self.operator_class = operator_class
self.op1_class = op1_class
@ -29,13 +56,13 @@ class AND:
def has_permission(self, request, view):
return (
self.op1.has_permission(request, view) &
self.op1.has_permission(request, view) and
self.op2.has_permission(request, view)
)
def has_object_permission(self, request, view, obj):
return (
self.op1.has_object_permission(request, view, obj) &
self.op1.has_object_permission(request, view, obj) and
self.op2.has_object_permission(request, view, obj)
)
@ -47,29 +74,30 @@ class OR:
def has_permission(self, request, view):
return (
self.op1.has_permission(request, view) |
self.op1.has_permission(request, view) or
self.op2.has_permission(request, view)
)
def has_object_permission(self, request, view, obj):
return (
self.op1.has_object_permission(request, view, obj) |
self.op1.has_object_permission(request, view, obj) or
self.op2.has_object_permission(request, view, obj)
)
class BasePermissionMetaclass(type):
def __and__(cls, other):
return OperandHolder(AND, cls, other)
class NOT:
def __init__(self, op1):
self.op1 = op1
def __or__(cls, other):
return OperandHolder(OR, cls, other)
def has_permission(self, request, view):
return not self.op1.has_permission(request, view)
def __rand__(cls, other):
return OperandHolder(AND, other, cls)
def has_object_permission(self, request, view, obj):
return not self.op1.has_object_permission(request, view, obj)
def __ror__(cls, other):
return OperandHolder(OR, other, cls)
class BasePermissionMetaclass(OperationHolderMixin, type):
pass
@six.add_metaclass(BasePermissionMetaclass)
@ -109,7 +137,7 @@ class IsAuthenticated(BasePermission):
"""
def has_permission(self, request, view):
return request.user and request.user.is_authenticated
return bool(request.user and request.user.is_authenticated)
class IsAdminUser(BasePermission):
@ -118,7 +146,7 @@ class IsAdminUser(BasePermission):
"""
def has_permission(self, request, view):
return request.user and request.user.is_staff
return bool(request.user and request.user.is_staff)
class IsAuthenticatedOrReadOnly(BasePermission):
@ -127,7 +155,7 @@ class IsAuthenticatedOrReadOnly(BasePermission):
"""
def has_permission(self, request, view):
return (
return bool(
request.method in SAFE_METHODS or
request.user and
request.user.is_authenticated

View File

@ -20,11 +20,12 @@ from django.test.client import encode_multipart
from django.urls import NoReverseMatch
from django.utils import six
from django.utils.html import mark_safe
from django.utils.six.moves.urllib import parse as urlparse
from rest_framework import VERSION, exceptions, serializers, status
from rest_framework.compat import (
INDENT_SEPARATORS, LONG_SEPARATORS, SHORT_SEPARATORS, coreapi, coreschema,
pygments_css, urlparse, yaml
pygments_css, yaml
)
from rest_framework.exceptions import ParseError
from rest_framework.request import is_form_media_type, override_method
@ -947,7 +948,7 @@ class _BaseOpenAPIRenderer:
schema = {}
if instance.__class__ in CLASS_TO_TYPENAME:
schema['type'] = CLASS_TO_TYPENAME[instance.__class__]
schema['title'] = instance.title,
schema['title'] = instance.title
schema['description'] = instance.description
if hasattr(instance, 'enum'):
schema['enum'] = instance.enum

View File

@ -24,7 +24,9 @@ from django.urls import NoReverseMatch
from django.utils import six
from django.utils.deprecation import RenameMethodsBase
from rest_framework import views
from rest_framework import (
RemovedInDRF310Warning, RemovedInDRF311Warning, views
)
from rest_framework.response import Response
from rest_framework.reverse import reverse
from rest_framework.schemas import SchemaGenerator
@ -42,7 +44,7 @@ class DynamicDetailRoute(object):
"`DynamicDetailRoute` is deprecated and will be removed in 3.10 "
"in favor of `DynamicRoute`, which accepts a `detail` boolean. Use "
"`DynamicRoute(url, name, True, initkwargs)` instead.",
DeprecationWarning, stacklevel=2
RemovedInDRF310Warning, stacklevel=2
)
return DynamicRoute(url, name, True, initkwargs)
@ -53,7 +55,7 @@ class DynamicListRoute(object):
"`DynamicListRoute` is deprecated and will be removed in 3.10 in "
"favor of `DynamicRoute`, which accepts a `detail` boolean. Use "
"`DynamicRoute(url, name, False, initkwargs)` instead.",
DeprecationWarning, stacklevel=2
RemovedInDRF310Warning, stacklevel=2
)
return DynamicRoute(url, name, False, initkwargs)
@ -76,7 +78,7 @@ def flatten(list_of_lists):
class RenameRouterMethods(RenameMethodsBase):
renamed_methods = (
('get_default_base_name', 'get_default_basename', PendingDeprecationWarning),
('get_default_base_name', 'get_default_basename', RemovedInDRF311Warning),
)
@ -87,7 +89,7 @@ class BaseRouter(six.with_metaclass(RenameRouterMethods)):
def register(self, prefix, viewset, basename=None, base_name=None):
if base_name is not None:
msg = "The `base_name` argument is pending deprecation in favor of `basename`."
warnings.warn(msg, PendingDeprecationWarning, 2)
warnings.warn(msg, RemovedInDRF311Warning, 2)
assert not (basename and base_name), (
"Do not provide both the `basename` and `base_name` arguments.")
@ -99,6 +101,10 @@ class BaseRouter(six.with_metaclass(RenameRouterMethods)):
basename = self.get_default_basename(viewset)
self.registry.append((prefix, viewset, basename))
# invalidate the urls cache
if hasattr(self, '_urls'):
del self._urls
def get_default_basename(self, viewset):
"""
If `basename` is not specified, attempt to automatically determine

View File

@ -57,7 +57,7 @@ Schema Naming Collision.
coreapi.Link for URL path {value_url} cannot be inserted into schema.
Position conflicts with coreapi.Link for URL path {target_url}.
Attemped to insert link with keys: {keys}.
Attempted to insert link with keys: {keys}.
Adjust URLs to avoid naming collision or override `SchemaGenerator.get_keys()`
to customise schema structure.

View File

@ -162,7 +162,10 @@ class ViewInspector:
@property
def view(self):
"""View property."""
assert self._view is not None, "Schema generation REQUIRES a view instance. (Hint: you accessed `schema` from the view class rather than an instance.)"
assert self._view is not None, (
"Schema generation REQUIRES a view instance. (Hint: you accessed "
"`schema` from the view class rather than an instance.)"
)
return self._view
@view.setter
@ -192,7 +195,7 @@ class AutoSchema(ViewInspector):
"""
Default inspector for APIView
Responsible for per-view instrospection and schema generation.
Responsible for per-view introspection and schema generation.
"""
def __init__(self, manual_fields=None):
"""
@ -467,7 +470,7 @@ class ManualSchema(ViewInspector):
Parameters:
* `fields`: list of `coreapi.Field` instances.
* `descripton`: String description for view. Optional.
* `description`: String description for view. Optional.
"""
super().__init__()
assert all(isinstance(f, coreapi.Field) for f in fields), "`fields` must be a list of coreapi.Field instances"
@ -497,7 +500,9 @@ class DefaultSchema(ViewInspector):
return result
inspector_class = api_settings.DEFAULT_SCHEMA_CLASS
assert issubclass(inspector_class, ViewInspector), "DEFAULT_SCHEMA_CLASS must be set to a ViewInspector (usually an AutoSchema) subclass"
assert issubclass(inspector_class, ViewInspector), (
"DEFAULT_SCHEMA_CLASS must be set to a ViewInspector (usually an AutoSchema) subclass"
)
inspector = inspector_class()
inspector.view = instance
return inspector

View File

@ -31,3 +31,11 @@ class SchemaView(APIView):
if schema is None:
raise exceptions.PermissionDenied()
return Response(schema)
def handle_exception(self, exc):
# Schema renderers do not render exceptions, so re-perform content
# negotiation with default renderers.
self.renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
neg = self.perform_content_negotiation(self.request, force=True)
self.request.accepted_renderer, self.request.accepted_media_type = neg
return super(SchemaView, self).handle_exception(exc)

View File

@ -392,7 +392,7 @@ class Serializer(BaseSerializer):
# Used by the lazily-evaluated `validators` property.
meta = getattr(self, 'Meta', None)
validators = getattr(meta, 'validators', None)
return validators[:] if validators else []
return list(validators) if validators else []
def get_initial(self):
if hasattr(self, 'initial_data'):
@ -460,8 +460,11 @@ class Serializer(BaseSerializer):
"""
Add read_only fields with defaults to value before running validators.
"""
to_validate = self._read_only_defaults()
to_validate.update(value)
if isinstance(value, dict):
to_validate = self._read_only_defaults()
to_validate.update(value)
else:
to_validate = value
super(Serializer, self).run_validators(to_validate)
def to_internal_value(self, data):
@ -1476,7 +1479,7 @@ class ModelSerializer(Serializer):
# If the validators have been declared explicitly then use that.
validators = getattr(getattr(self, 'Meta', None), 'validators', None)
if validators is not None:
return validators[:]
return list(validators)
# Otherwise use the default set of validators.
return (

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

View File

View File

View File

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 197 KiB

View File

View File

File diff suppressed because one or more lines are too long

View File

@ -38,7 +38,7 @@ function sameOrigin(url) {
!(/^(\/\/|http:|https:).*/.test(url));
}
var csrftoken = getCookie(window.drf.csrfCookieName);
var csrftoken = window.drf.csrfToken;
$.ajaxSetup({
beforeSend: function(xhr, settings) {

View File

@ -247,7 +247,7 @@
<script>
window.drf = {
csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}",
csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}"
csrfToken: "{{ csrf_token }}"
};
</script>
<script src="{% static "rest_framework/js/jquery-3.3.1.min.js" %}"></script>

View File

@ -76,6 +76,8 @@
{% block content %}
<div class="region" aria-label="{% trans "request form" %}">
{% block request_forms %}
{% if 'GET' in allowed_methods %}
<form id="get-form" class="pull-right">
<fieldset>
@ -148,6 +150,8 @@
{% trans "Filters" %}
</button>
{% endif %}
{% endblock request_forms %}
</div>
<div class="content-main" role="main" aria-label="{% trans "main content" %}">
@ -171,10 +175,10 @@
</div>
<div class="response-info" aria-label="{% trans "response info" %}">
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% autoescape off %}{% for key, val in response_headers|items %}
<pre class="prettyprint"><span class="meta nocode"><b>HTTP {{ response.status_code }} {{ response.status_text }}</b>{% for key, val in response_headers|items %}
<b>{{ key }}:</b> <span class="lit">{{ val|break_long_headers|urlize_quoted_links }}</span>{% endfor %}
</span>{{ content|urlize_quoted_links }}</pre>{% endautoescape %}
</span>{{ content|urlize_quoted_links }}</pre>
</div>
</div>
@ -201,7 +205,7 @@
{% csrf_token %}
{{ post_form }}
<div class="form-actions">
<button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
<button class="btn btn-primary js-tooltip" title="Make a POST request on the {{ name }} resource">POST</button>
</div>
</fieldset>
</form>
@ -215,7 +219,7 @@
<fieldset>
{% include "rest_framework/raw_data_form.html" %}
<div class="form-actions">
<button class="btn btn-primary" title="Make a POST request on the {{ name }} resource">POST</button>
<button class="btn btn-primary js-tooltip" title="Make a POST request on the {{ name }} resource">POST</button>
</div>
</fieldset>
</form>
@ -286,7 +290,7 @@
<script>
window.drf = {
csrfHeaderName: "{{ csrf_header_name|default:'X-CSRFToken' }}",
csrfCookieName: "{{ csrf_cookie_name|default:'csrftoken' }}"
csrfToken: "{% if request %}{{ csrf_token }}{% endif %}"
};
</script>
<script src="{% static "rest_framework/js/jquery-3.3.1.min.js" %}"></script>

View File

@ -3,4 +3,4 @@
$ coreapi get {{ document.url }}{% if schema_format %} --format {{ schema_format }}{% endif %}
# Interact with the API endpoint
$ coreapi action {% if section_key %}{{ section_key }} {% endif %}{{ link_key }}{% for field in link.fields %} -p {{ field.name }}=...{% endfor %}{% endcode %}</code></pre>
$ coreapi action {% if section_key %}{{ section_key }} {% endif %}{{ link_key|cut:"> " }}{% for field in link.fields %} -p {{ field.name }}=...{% endfor %}{% endcode %}</code></pre>

View File

@ -334,6 +334,12 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
return limit is not None and (len(x) > limit and ('%s...' % x[:max(0, limit - 3)])) or x
safe_input = isinstance(text, SafeData)
# Unfortunately, Django built-in cannot be used here, because escaping
# is to be performed on words, which have been forcibly coerced to text
def conditional_escape(text):
return escape(text) if autoescape and not safe_input else text
words = word_split_re.split(force_text(text))
for i, word in enumerate(words):
if '.' in word or '@' in word or ':' in word:
@ -374,21 +380,15 @@ def urlize_quoted_links(text, trim_url_limit=None, nofollow=True, autoescape=Tru
# Make link.
if url:
trimmed = trim_url(middle)
if autoescape and not safe_input:
lead, trail = escape(lead), escape(trail)
url, trimmed = escape(url), escape(trimmed)
lead, trail = conditional_escape(lead), conditional_escape(trail)
url, trimmed = conditional_escape(url), conditional_escape(trimmed)
middle = '<a href="%s"%s>%s</a>' % (url, nofollow_attr, trimmed)
words[i] = mark_safe('%s%s%s' % (lead, middle, trail))
words[i] = '%s%s%s' % (lead, middle, trail)
else:
if safe_input:
words[i] = mark_safe(word)
elif autoescape:
words[i] = escape(word)
elif safe_input:
words[i] = mark_safe(word)
elif autoescape:
words[i] = escape(word)
return ''.join(words)
words[i] = conditional_escape(word)
else:
words[i] = conditional_escape(word)
return mark_safe(''.join(words))
@register.filter

View File

@ -88,6 +88,9 @@ def get_field_kwargs(field_name, model_field):
if decimal_places is not None:
kwargs['decimal_places'] = decimal_places
if isinstance(model_field, models.SlugField):
kwargs['allow_unicode'] = model_field.allow_unicode
if isinstance(model_field, models.TextField) or (postgres_fields and isinstance(model_field, postgres_fields.JSONField)):
kwargs['style'] = {'base_template': 'textarea.html'}
@ -246,6 +249,12 @@ def get_relation_kwargs(field_name, relation_info):
if to_field:
kwargs['to_field'] = to_field
limit_choices_to = model_field and model_field.get_limit_choices_to()
if limit_choices_to:
if not isinstance(limit_choices_to, models.Q):
limit_choices_to = models.Q(**limit_choices_to)
kwargs['queryset'] = kwargs['queryset'].filter(limit_choices_to)
if has_through_model:
kwargs['read_only'] = True
kwargs.pop('queryset', None)

View File

@ -1,9 +1,8 @@
import collections
from collections import OrderedDict
from django.utils.encoding import force_text
from rest_framework.compat import unicode_to_repr
from rest_framework.compat import MutableMapping, unicode_to_repr
from rest_framework.utils import json
@ -128,7 +127,7 @@ class NestedBoundField(BoundField):
return self.__class__(self._field, values, self.errors, self._prefix)
class BindingDict(collections.MutableMapping):
class BindingDict(MutableMapping):
"""
This dict-like object is used to store fields on a serializer.

View File

@ -72,6 +72,9 @@ class URLPathVersioning(BaseVersioning):
def determine_version(self, request, *args, **kwargs):
version = kwargs.get(self.version_param, self.default_version)
if version is None:
version = self.default_version
if not self.is_allowed_version(version):
raise exceptions.NotFound(self.invalid_version_message)
return version

View File

@ -61,6 +61,7 @@ setup(
'Framework :: Django :: 1.11',
'Framework :: Django :: 2.0',
'Framework :: Django :: 2.1',
'Framework :: Django :: 2.2',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
@ -71,6 +72,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
]
)

View File

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='CustomToken',
fields=[
('key', models.CharField(max_length=40, primary_key=True, serialize=False)),
('user', models.OneToOneField(on_delete=models.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -0,0 +1,10 @@
# coding: utf-8
from __future__ import unicode_literals
from django.conf import settings
from django.db import models
class CustomToken(models.Model):
key = models.CharField(max_length=40, primary_key=True)
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

View File

@ -4,7 +4,6 @@ import pytest
from django.conf import settings
from django.conf.urls import include, url
from django.contrib.auth.models import User
from django.db import models
from django.http import HttpResponse
from django.test import TestCase, override_settings
from django.utils import six
@ -22,14 +21,11 @@ from rest_framework.response import Response
from rest_framework.test import APIClient, APIRequestFactory
from rest_framework.views import APIView
from .models import CustomToken
factory = APIRequestFactory()
class CustomToken(models.Model):
key = models.CharField(max_length=40, primary_key=True)
user = models.OneToOneField(User, on_delete=models.CASCADE)
class CustomTokenAuthentication(TokenAuthentication):
model = CustomToken
@ -83,7 +79,7 @@ urlpatterns = [
]
@override_settings(ROOT_URLCONF='tests.test_authentication')
@override_settings(ROOT_URLCONF=__name__)
class BasicAuthTests(TestCase):
"""Basic authentication"""
def setUp(self):
@ -165,7 +161,7 @@ class BasicAuthTests(TestCase):
assert response.status_code == status.HTTP_401_UNAUTHORIZED
@override_settings(ROOT_URLCONF='tests.test_authentication')
@override_settings(ROOT_URLCONF=__name__)
class SessionAuthTests(TestCase):
"""User session authentication"""
def setUp(self):
@ -366,7 +362,7 @@ class BaseTokenAuthTests(object):
assert response.status_code == status.HTTP_401_UNAUTHORIZED
@override_settings(ROOT_URLCONF='tests.test_authentication')
@override_settings(ROOT_URLCONF=__name__)
class TokenAuthTests(BaseTokenAuthTests, TestCase):
model = Token
path = '/token/'
@ -425,13 +421,13 @@ class TokenAuthTests(BaseTokenAuthTests, TestCase):
assert response.data['token'] == self.key
@override_settings(ROOT_URLCONF='tests.test_authentication')
@override_settings(ROOT_URLCONF=__name__)
class CustomTokenAuthTests(BaseTokenAuthTests, TestCase):
model = CustomToken
path = '/customtoken/'
@override_settings(ROOT_URLCONF='tests.test_authentication')
@override_settings(ROOT_URLCONF=__name__)
class CustomKeywordTokenAuthTests(BaseTokenAuthTests, TestCase):
model = Token
path = '/customkeywordtoken/'
@ -545,7 +541,7 @@ class BasicAuthenticationUnitTests(TestCase):
authentication.authenticate = old_authenticate
@override_settings(ROOT_URLCONF='tests.test_authentication',
@override_settings(ROOT_URLCONF=__name__,
AUTHENTICATION_BACKENDS=('django.contrib.auth.backends.RemoteUserBackend',))
class RemoteUserAuthenticationUnitTests(TestCase):
def setUp(self):

View File

@ -56,6 +56,8 @@ def pytest_configure(config):
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'tests.authentication',
'tests.generic_relations',
'tests.importable',
'tests',
),

View File

View File

@ -0,0 +1,36 @@
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
]
operations = [
migrations.CreateModel(
name='Bookmark',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('url', models.URLField()),
],
),
migrations.CreateModel(
name='Note',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('text', models.TextField()),
],
),
migrations.CreateModel(
name='Tag',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tag', models.SlugField()),
('object_id', models.PositiveIntegerField()),
('content_type', models.ForeignKey(on_delete=models.CASCADE, to='contenttypes.ContentType')),
],
),
]

View File

@ -0,0 +1,46 @@
from __future__ import unicode_literals
from django.contrib.contenttypes.fields import (
GenericForeignKey, GenericRelation
)
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.utils.encoding import python_2_unicode_compatible
@python_2_unicode_compatible
class Tag(models.Model):
"""
Tags have a descriptive slug, and are attached to an arbitrary object.
"""
tag = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
tagged_item = GenericForeignKey('content_type', 'object_id')
def __str__(self):
return self.tag
@python_2_unicode_compatible
class Bookmark(models.Model):
"""
A URL bookmark that may have multiple tags attached.
"""
url = models.URLField()
tags = GenericRelation(Tag)
def __str__(self):
return 'Bookmark: %s' % self.url
@python_2_unicode_compatible
class Note(models.Model):
"""
A textual note that may have multiple tags attached.
"""
text = models.TextField()
tags = GenericRelation(Tag)
def __str__(self):
return 'Note: %s' % self.text

View File

@ -3,6 +3,7 @@ from django.contrib.contenttypes.fields import (
)
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.test import TestCase
from rest_framework import serializers

View File

@ -50,6 +50,20 @@ class ForeignKeySource(RESTFrameworkModel):
on_delete=models.CASCADE)
class ForeignKeySourceWithLimitedChoices(RESTFrameworkModel):
target = models.ForeignKey(ForeignKeyTarget, help_text='Target',
verbose_name='Target',
limit_choices_to={"name__startswith": "limited-"},
on_delete=models.CASCADE)
class ForeignKeySourceWithQLimitedChoices(RESTFrameworkModel):
target = models.ForeignKey(ForeignKeyTarget, help_text='Target',
verbose_name='Target',
limit_choices_to=models.Q(name__startswith="limited-"),
on_delete=models.CASCADE)
# Nullable ForeignKey
class NullableForeignKeySource(RESTFrameworkModel):
name = models.CharField(max_length=100)

View File

@ -1,7 +1,7 @@
import pytest
from django.test import TestCase
from rest_framework import status
from rest_framework import RemovedInDRF310Warning, status
from rest_framework.authentication import BasicAuthentication
from rest_framework.decorators import (
action, api_view, authentication_classes, detail_route, list_route,
@ -288,7 +288,7 @@ class ActionDecoratorTestCase(TestCase):
raise NotImplementedError
def test_detail_route_deprecation(self):
with pytest.warns(DeprecationWarning) as record:
with pytest.warns(RemovedInDRF310Warning) as record:
@detail_route()
def view(request):
raise NotImplementedError
@ -301,7 +301,7 @@ class ActionDecoratorTestCase(TestCase):
)
def test_list_route_deprecation(self):
with pytest.warns(DeprecationWarning) as record:
with pytest.warns(RemovedInDRF310Warning) as record:
@list_route()
def view(request):
raise NotImplementedError
@ -315,7 +315,7 @@ class ActionDecoratorTestCase(TestCase):
def test_route_url_name_from_path(self):
# pre-3.8 behavior was to base the `url_name` off of the `url_path`
with pytest.warns(DeprecationWarning):
with pytest.warns(RemovedInDRF310Warning):
@list_route(url_path='foo_bar')
def view(request):
raise NotImplementedError

View File

@ -740,6 +740,25 @@ class TestCharField(FieldValues):
'Null characters are not allowed.'
]
def test_iterable_validators(self):
"""
Ensure `validators` parameter is compatible with reasonable iterables.
"""
value = 'example'
for validators in ([], (), set()):
field = serializers.CharField(validators=validators)
field.run_validation(value)
def raise_exception(value):
raise exceptions.ValidationError('Raised error')
for validators in ([raise_exception], (raise_exception,), set([raise_exception])):
field = serializers.CharField(validators=validators)
with pytest.raises(serializers.ValidationError) as exc_info:
field.run_validation(value)
assert exc_info.value.detail == ['Raised error']
class TestEmailField(FieldValues):
"""

View File

@ -3,6 +3,7 @@ import datetime
import pytest
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.db.models.functions import Concat, Upper
from django.test import TestCase
from django.test.utils import override_settings
from django.utils.six.moves import reload_module
@ -154,6 +155,31 @@ class SearchFilterTests(TestCase):
reload_module(filters)
def test_search_with_filter_subclass(self):
class CustomSearchFilter(filters.SearchFilter):
# Filter that dynamically changes search fields
def get_search_fields(self, view, request):
if request.query_params.get('title_only'):
return ('$title',)
return super(CustomSearchFilter, self).get_search_fields(view, request)
class SearchListView(generics.ListAPIView):
queryset = SearchFilterModel.objects.all()
serializer_class = SearchFilterSerializer
filter_backends = (CustomSearchFilter,)
search_fields = ('$title', '$text')
view = SearchListView.as_view()
request = factory.get('/', {'search': '^\w{3}$'})
response = view(request)
assert len(response.data) == 10
request = factory.get('/', {'search': '^\w{3}$', 'title_only': 'true'})
response = view(request)
assert response.data == [
{'id': 3, 'title': 'zzz', 'text': 'cde'}
]
class AttributeModel(models.Model):
label = models.CharField(max_length=32)
@ -219,7 +245,7 @@ class SearchFilterM2MTests(TestCase):
# ...
for idx in range(3):
label = 'w' * (idx + 1)
AttributeModel(label=label)
AttributeModel.objects.create(label=label)
for idx in range(10):
title = 'z' * (idx + 1)
@ -302,6 +328,38 @@ class SearchFilterToManyTests(TestCase):
assert len(response.data) == 1
class SearchFilterAnnotatedSerializer(serializers.ModelSerializer):
title_text = serializers.CharField()
class Meta:
model = SearchFilterModel
fields = ('title', 'text', 'title_text')
class SearchFilterAnnotatedFieldTests(TestCase):
@classmethod
def setUpTestData(cls):
SearchFilterModel.objects.create(title='abc', text='def')
SearchFilterModel.objects.create(title='ghi', text='jkl')
def test_search_in_annotated_field(self):
class SearchListView(generics.ListAPIView):
queryset = SearchFilterModel.objects.annotate(
title_text=Upper(
Concat(models.F('title'), models.F('text'))
)
).all()
serializer_class = SearchFilterAnnotatedSerializer
filter_backends = (filters.SearchFilter,)
search_fields = ('title_text',)
view = SearchListView.as_view()
request = factory.get('/', {'search': 'ABCDEF'})
response = view(request)
assert len(response.data) == 1
assert response.data[0]['title_text'] == 'ABCDEF'
class OrderingFilterModel(models.Model):
title = models.CharField(max_length=20, verbose_name='verbose title')
text = models.CharField(max_length=100)

View File

@ -0,0 +1,88 @@
from __future__ import unicode_literals
import pytest
from django.conf.urls import url
from django.core.management import call_command
from django.test import TestCase
from django.test.utils import override_settings
from django.utils import six
from rest_framework.compat import coreapi
from rest_framework.utils import formatting, json
from rest_framework.views import APIView
class FooView(APIView):
def get(self, request):
pass
urlpatterns = [
url(r'^$', FooView.as_view())
]
@override_settings(ROOT_URLCONF='tests.test_generateschema')
@pytest.mark.skipif(not coreapi, reason='coreapi is not installed')
class GenerateSchemaTests(TestCase):
"""Tests for management command generateschema."""
def setUp(self):
self.out = six.StringIO()
@pytest.mark.skipif(six.PY2, reason='PyYAML unicode output is malformed on PY2.')
def test_renders_default_schema_with_custom_title_url_and_description(self):
expected_out = """info:
description: Sample description
title: SampleAPI
version: ''
openapi: 3.0.0
paths:
/:
get:
operationId: list
servers:
- url: http://api.sample.com/
"""
call_command('generateschema',
'--title=SampleAPI',
'--url=http://api.sample.com',
'--description=Sample description',
stdout=self.out)
self.assertIn(formatting.dedent(expected_out), self.out.getvalue())
def test_renders_openapi_json_schema(self):
expected_out = {
"openapi": "3.0.0",
"info": {
"version": "",
"title": "",
"description": ""
},
"servers": [
{
"url": ""
}
],
"paths": {
"/": {
"get": {
"operationId": "list"
}
}
}
}
call_command('generateschema',
'--format=openapi-json',
stdout=self.out)
out_json = json.loads(self.out.getvalue())
self.assertDictEqual(out_json, expected_out)
def test_renders_corejson_schema(self):
expected_out = """{"_type":"document","":{"list":{"_type":"link","url":"/","action":"get"}}}"""
call_command('generateschema',
'--format=corejson',
stdout=self.out)
self.assertIn(expected_out, self.out.getvalue())

View File

@ -181,7 +181,7 @@ class TestRegularFieldMappings(TestCase):
null_boolean_field = NullBooleanField(required=False)
positive_integer_field = IntegerField()
positive_small_integer_field = IntegerField()
slug_field = SlugField(max_length=100)
slug_field = SlugField(allow_unicode=False, max_length=100)
small_integer_field = IntegerField()
text_field = CharField(max_length=100, style={'base_template': 'textarea.html'})
file_field = FileField(max_length=100)

View File

@ -3,16 +3,17 @@ import unittest
import warnings
import django
from django.contrib.auth.models import Group, Permission, User
import pytest
from django.contrib.auth.models import AnonymousUser, Group, Permission, User
from django.db import models
from django.test import TestCase
from django.urls import ResolverMatch
from rest_framework import (
HTTP_HEADER_ENCODING, authentication, generics, permissions, serializers,
status, views
HTTP_HEADER_ENCODING, RemovedInDRF310Warning, authentication, generics,
permissions, serializers, status, views
)
from rest_framework.compat import is_guardian_installed
from rest_framework.compat import PY36, is_guardian_installed, mock
from rest_framework.filters import DjangoObjectPermissionsFilter
from rest_framework.routers import DefaultRouter
from rest_framework.test import APIRequestFactory
@ -424,7 +425,7 @@ class ObjectPermissionsIntegrationTests(TestCase):
message = ("`DjangoObjectPermissionsFilter` has been deprecated and moved "
"to the 3rd-party django-rest-framework-guardian package.")
self.assertEqual(len(w), 1)
self.assertIs(w[-1].category, DeprecationWarning)
self.assertIs(w[-1].category, RemovedInDRF310Warning)
self.assertEqual(str(w[-1].message), message)
def test_can_read_list_permissions(self):
@ -540,39 +541,58 @@ class CustomPermissionsTests(TestCase):
self.assertEqual(detail, self.custom_message)
class FakeUser:
def __init__(self, auth=False):
self.is_authenticated = auth
class PermissionsCompositionTests(TestCase):
def setUp(self):
self.username = 'john'
self.email = 'lennon@thebeatles.com'
self.password = 'password'
self.user = User.objects.create_user(
self.username,
self.email,
self.password
)
self.client.login(username=self.username, password=self.password)
def test_and_false(self):
request = factory.get('/1', format='json')
request.user = FakeUser(auth=False)
request.user = AnonymousUser()
composed_perm = permissions.IsAuthenticated & permissions.AllowAny
assert composed_perm().has_permission(request, None) is False
def test_and_true(self):
request = factory.get('/1', format='json')
request.user = FakeUser(auth=True)
request.user = self.user
composed_perm = permissions.IsAuthenticated & permissions.AllowAny
assert composed_perm().has_permission(request, None) is True
def test_or_false(self):
request = factory.get('/1', format='json')
request.user = FakeUser(auth=False)
request.user = AnonymousUser()
composed_perm = permissions.IsAuthenticated | permissions.AllowAny
assert composed_perm().has_permission(request, None) is True
def test_or_true(self):
request = factory.get('/1', format='json')
request.user = FakeUser(auth=True)
request.user = self.user
composed_perm = permissions.IsAuthenticated | permissions.AllowAny
assert composed_perm().has_permission(request, None) is True
def test_several_levels(self):
def test_not_false(self):
request = factory.get('/1', format='json')
request.user = FakeUser(auth=True)
request.user = AnonymousUser()
composed_perm = ~permissions.IsAuthenticated
assert composed_perm().has_permission(request, None) is True
def test_not_true(self):
request = factory.get('/1', format='json')
request.user = self.user
composed_perm = ~permissions.AllowAny
assert composed_perm().has_permission(request, None) is False
def test_several_levels_without_negation(self):
request = factory.get('/1', format='json')
request.user = self.user
composed_perm = (
permissions.IsAuthenticated &
permissions.IsAuthenticated &
@ -580,3 +600,109 @@ class PermissionsCompositionTests(TestCase):
permissions.IsAuthenticated
)
assert composed_perm().has_permission(request, None) is True
def test_several_levels_and_precedence_with_negation(self):
request = factory.get('/1', format='json')
request.user = self.user
composed_perm = (
permissions.IsAuthenticated &
~ permissions.IsAdminUser &
permissions.IsAuthenticated &
~(permissions.IsAdminUser & permissions.IsAdminUser)
)
assert composed_perm().has_permission(request, None) is True
def test_several_levels_and_precedence(self):
request = factory.get('/1', format='json')
request.user = self.user
composed_perm = (
permissions.IsAuthenticated &
permissions.IsAuthenticated |
permissions.IsAuthenticated &
permissions.IsAuthenticated
)
assert composed_perm().has_permission(request, None) is True
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
def test_or_lazyness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
composed_perm = (permissions.AllowAny | permissions.IsAuthenticated)
hasperm = composed_perm().has_permission(request, None)
self.assertIs(hasperm, True)
mock_allow.assert_called_once()
mock_deny.assert_not_called()
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
composed_perm = (permissions.IsAuthenticated | permissions.AllowAny)
hasperm = composed_perm().has_permission(request, None)
self.assertIs(hasperm, True)
mock_deny.assert_called_once()
mock_allow.assert_called_once()
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
def test_object_or_lazyness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
composed_perm = (permissions.AllowAny | permissions.IsAuthenticated)
hasperm = composed_perm().has_object_permission(request, None, None)
self.assertIs(hasperm, True)
mock_allow.assert_called_once()
mock_deny.assert_not_called()
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
composed_perm = (permissions.IsAuthenticated | permissions.AllowAny)
hasperm = composed_perm().has_object_permission(request, None, None)
self.assertIs(hasperm, True)
mock_deny.assert_called_once()
mock_allow.assert_called_once()
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
def test_and_lazyness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
composed_perm = (permissions.AllowAny & permissions.IsAuthenticated)
hasperm = composed_perm().has_permission(request, None)
self.assertIs(hasperm, False)
mock_allow.assert_called_once()
mock_deny.assert_called_once()
with mock.patch.object(permissions.AllowAny, 'has_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_permission', return_value=False) as mock_deny:
composed_perm = (permissions.IsAuthenticated & permissions.AllowAny)
hasperm = composed_perm().has_permission(request, None)
self.assertIs(hasperm, False)
mock_allow.assert_not_called()
mock_deny.assert_called_once()
@pytest.mark.skipif(not PY36, reason="assert_called_once() not available")
def test_object_and_lazyness(self):
request = factory.get('/1', format='json')
request.user = AnonymousUser()
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
composed_perm = (permissions.AllowAny & permissions.IsAuthenticated)
hasperm = composed_perm().has_object_permission(request, None, None)
self.assertIs(hasperm, False)
mock_allow.assert_called_once()
mock_deny.assert_called_once()
with mock.patch.object(permissions.AllowAny, 'has_object_permission', return_value=True) as mock_allow:
with mock.patch.object(permissions.IsAuthenticated, 'has_object_permission', return_value=False) as mock_deny:
composed_perm = (permissions.IsAuthenticated & permissions.AllowAny)
hasperm = composed_perm().has_object_permission(request, None, None)
self.assertIs(hasperm, False)
mock_allow.assert_not_called()
mock_deny.assert_called_once()

View File

@ -3,8 +3,9 @@ from django.utils import six
from rest_framework import serializers
from tests.models import (
ForeignKeySource, ForeignKeyTarget, ManyToManySource, ManyToManyTarget,
NullableForeignKeySource, NullableOneToOneSource,
ForeignKeySource, ForeignKeySourceWithLimitedChoices,
ForeignKeySourceWithQLimitedChoices, ForeignKeyTarget, ManyToManySource,
ManyToManyTarget, NullableForeignKeySource, NullableOneToOneSource,
NullableUUIDForeignKeySource, OneToOnePKSource, OneToOneTarget,
UUIDForeignKeyTarget
)
@ -36,6 +37,12 @@ class ForeignKeySourceSerializer(serializers.ModelSerializer):
fields = ('id', 'name', 'target')
class ForeignKeySourceWithLimitedChoicesSerializer(serializers.ModelSerializer):
class Meta:
model = ForeignKeySourceWithLimitedChoices
fields = ("id", "target")
# Nullable ForeignKey
class NullableForeignKeySourceSerializer(serializers.ModelSerializer):
class Meta:
@ -358,6 +365,30 @@ class PKForeignKeyTests(TestCase):
serializer.is_valid(raise_exception=True)
assert 'target' not in serializer.validated_data
def test_queryset_size_without_limited_choices(self):
limited_target = ForeignKeyTarget(name="limited-target")
limited_target.save()
queryset = ForeignKeySourceSerializer().fields["target"].get_queryset()
assert len(queryset) == 3
def test_queryset_size_with_limited_choices(self):
limited_target = ForeignKeyTarget(name="limited-target")
limited_target.save()
queryset = ForeignKeySourceWithLimitedChoicesSerializer().fields["target"].get_queryset()
assert len(queryset) == 1
def test_queryset_size_with_Q_limited_choices(self):
limited_target = ForeignKeyTarget(name="limited-target")
limited_target.save()
class QLimitedChoicesSerializer(serializers.ModelSerializer):
class Meta:
model = ForeignKeySourceWithQLimitedChoices
fields = ("id", "target")
queryset = QLimitedChoicesSerializer().fields["target"].get_queryset()
assert len(queryset) == 1
class PKNullableForeignKeyTests(TestCase):
def setUp(self):

View File

@ -1,18 +1,19 @@
import re
from collections import MutableMapping, OrderedDict
from collections import OrderedDict
import pytest
from django.conf.urls import include, url
from django.core.cache import cache
from django.db import models
from django.http.request import HttpRequest
from django.template import loader
from django.test import TestCase, override_settings
from django.utils import six
from django.utils.safestring import SafeText
from django.utils.translation import ugettext_lazy as _
from rest_framework import permissions, serializers, status
from rest_framework.compat import coreapi
from rest_framework.compat import MutableMapping, coreapi
from rest_framework.decorators import action
from rest_framework.renderers import (
AdminRenderer, BaseRenderer, BrowsableAPIRenderer, DocumentationRenderer,
@ -824,6 +825,16 @@ class TestDocumentationRenderer(TestCase):
html = renderer.render(document, accepted_media_type="text/html", renderer_context={"request": request})
assert '<h1>Data Endpoint API</h1>' in html
def test_shell_code_example_rendering(self):
template = loader.get_template('rest_framework/docs/langs/shell.html')
context = {
'document': coreapi.Document(url='https://api.example.org/'),
'link_key': 'testcases > list',
'link': coreapi.Link(url='/data/', action='get', fields=[]),
}
html = template.render(context)
assert 'testcases list' in html
@pytest.mark.skipif(not coreapi, reason='coreapi is not installed')
class TestSchemaJSRenderer(TestCase):

Some files were not shown because too many files have changed in this diff Show More