diff --git a/docs/api-guide/authentication.md b/docs/api-guide/authentication.md index 3dc2acbc4..05b8523f8 100644 --- a/docs/api-guide/authentication.md +++ b/docs/api-guide/authentication.md @@ -330,7 +330,7 @@ The [Django OAuth2 Consumer][doac] library from [Rediker Software][rediker] is a ## 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. [Blimp][blimp] maintains the [djangorestframework-jwt][djangorestframework-jwt] package which provides a JWT Authentication class as well as a mechanism for clients to obtain a JWT given the username and password. +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. [Blimp][blimp] maintains the [djangorestframework-jwt][djangorestframework-jwt] package which provides a JWT Authentication class as well as a mechanism for clients to obtain a JWT given the username and password. An alternative package for JWT authentication is [djangorestframework-simplejwt][djangorestframework-simplejwt] which provides different features as well as a pluggable token blacklist app. ## Hawk HTTP Authentication @@ -388,6 +388,7 @@ HTTP Signature (currently a [IETF draft][http-signature-ietf-draft]) provides a [doac-rest-framework]: https://github.com/Rediker-Software/doac/blob/master/docs/integrations.md# [blimp]: https://github.com/GetBlimp [djangorestframework-jwt]: https://github.com/GetBlimp/django-rest-framework-jwt +[djangorestframework-simplejwt]: https://github.com/davesque/django-rest-framework-simplejwt [etoccalino]: https://github.com/etoccalino/ [djangorestframework-httpsignature]: https://github.com/etoccalino/django-rest-framework-httpsignature [amazon-http-signature]: http://docs.aws.amazon.com/general/latest/gr/signature-version-4.html diff --git a/docs/api-guide/pagination.md b/docs/api-guide/pagination.md index 888390018..d767e45de 100644 --- a/docs/api-guide/pagination.md +++ b/docs/api-guide/pagination.md @@ -242,29 +242,6 @@ We'd then need to setup the custom class in our configuration: Note that if you care about how the ordering of keys is displayed in responses in the browsable API you might choose to use an `OrderedDict` when constructing the body of paginated responses, but this is optional. -## Header based pagination - -Let's modify the built-in `PageNumberPagination` style, so that instead of include the pagination links in the body of the response, we'll instead include a `Link` header, in a [similar style to the GitHub API][github-link-pagination]. - - class LinkHeaderPagination(pagination.PageNumberPagination): - def get_paginated_response(self, data): - next_url = self.get_next_link() - previous_url = self.get_previous_link() - - if next_url is not None and previous_url is not None: - link = '<{next_url}>; rel="next", <{previous_url}>; rel="prev"' - elif next_url is not None: - link = '<{next_url}>; rel="next"' - elif previous_url is not None: - link = '<{previous_url}>; rel="prev"' - else: - link = '' - - link = link.format(next_url=next_url, previous_url=previous_url) - headers = {'Link': link} if link else {} - - return Response(data, headers=headers) - ## Using your custom pagination class To have your custom pagination class be used by default, use the `DEFAULT_PAGINATION_CLASS` setting: @@ -328,10 +305,15 @@ The [`DRF-extensions` package][drf-extensions] includes a [`PaginateByMaxMixin` The [`drf-proxy-pagination` package][drf-proxy-pagination] includes a `ProxyPagination` class which allows to choose pagination class with a query parameter. +## 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). + [cite]: https://docs.djangoproject.com/en/stable/topics/pagination/ [github-link-pagination]: https://developer.github.com/guides/traversing-with-pagination/ [link-header]: ../img/link-header-pagination.png [drf-extensions]: http://chibisov.github.io/drf-extensions/docs/ [paginate-by-max-mixin]: http://chibisov.github.io/drf-extensions/docs/#paginatebymaxmixin [drf-proxy-pagination]: https://github.com/tuffnatty/drf-proxy-pagination +[drf-link-header-pagination]: https://github.com/tbeadle/django-rest-framework-link-header-pagination [disqus-cursor-api]: http://cramer.io/2011/03/08/building-cursors-for-the-disqus-api diff --git a/docs/topics/third-party-packages.md b/docs/topics/third-party-packages.md index 44639f3d2..035335a82 100644 --- a/docs/topics/third-party-packages.md +++ b/docs/topics/third-party-packages.md @@ -185,6 +185,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque * [django-oauth-toolkit][django-oauth-toolkit] - Provides OAuth 2.0 support. * [doac][doac] - 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. * [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. @@ -284,6 +285,7 @@ To submit new content, [open an issue][drf-create-issue] or [create a pull reque [django-oauth-toolkit]: https://github.com/evonove/django-oauth-toolkit [doac]: https://github.com/Rediker-Software/doac [djangorestframework-jwt]: https://github.com/GetBlimp/django-rest-framework-jwt +[djangorestframework-simplejwt]: https://github.com/davesque/django-rest-framework-simplejwt [hawkrest]: https://github.com/kumar303/hawkrest [djangorestframework-httpsignature]: https://github.com/etoccalino/django-rest-framework-httpsignature [djoser]: https://github.com/sunscrapers/djoser diff --git a/rest_framework/authtoken/management/__init__.py b/rest_framework/authtoken/management/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rest_framework/authtoken/management/commands/__init__.py b/rest_framework/authtoken/management/commands/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/rest_framework/authtoken/management/commands/drf_create_token.py b/rest_framework/authtoken/management/commands/drf_create_token.py new file mode 100644 index 000000000..417bdd780 --- /dev/null +++ b/rest_framework/authtoken/management/commands/drf_create_token.py @@ -0,0 +1,45 @@ +from django.contrib.auth import get_user_model +from django.core.management.base import BaseCommand, CommandError +from rest_framework.authtoken.models import Token + + +UserModel = get_user_model() + + +class Command(BaseCommand): + help = 'Create DRF Token for a given user' + + def create_user_token(self, username, reset_token): + user = UserModel._default_manager.get_by_natural_key(username) + + if reset_token: + Token.objects.filter(user=user).delete() + + token = Token.objects.get_or_create(user=user) + return token[0] + + def add_arguments(self, parser): + parser.add_argument('username', type=str, nargs='+') + + parser.add_argument( + '-r', + '--reset', + action='store_true', + dest='reset_token', + default=False, + help='Reset existing User token and create a new one', + ) + + def handle(self, *args, **options): + username = options['username'] + reset_token = options['reset_token'] + + try: + token = self.create_user_token(username, reset_token) + except UserModel.DoesNotExist: + raise CommandError( + 'Cannot create the Token: user {0} does not exist'.format( + username) + ) + self.stdout.write( + 'Generated token {0} for user {1}'.format(token.key, username)) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 41d6105ca..14b264ff9 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -791,13 +791,17 @@ class RegexField(CharField): class SlugField(CharField): default_error_messages = { - 'invalid': _('Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.') + 'invalid': _('Enter a valid "slug" consisting of letters, numbers, underscores or hyphens.'), + 'invalid_unicode': _('Enter a valid "slug" consisting of Unicode letters, numbers, underscores, or hyphens.') } - def __init__(self, **kwargs): + def __init__(self, allow_unicode=False, **kwargs): super(SlugField, self).__init__(**kwargs) - slug_regex = re.compile(r'^[-a-zA-Z0-9_]+$') - validator = RegexValidator(slug_regex, message=self.error_messages['invalid']) + self.allow_unicode = allow_unicode + if self.allow_unicode: + validator = RegexValidator(re.compile(r'^[-\w]+\Z', re.UNICODE), message=self.error_messages['invalid_unicode']) + else: + validator = RegexValidator(re.compile(r'^[-a-zA-Z0-9_]+$'), message=self.error_messages['invalid']) self.validators.append(validator) @@ -1133,18 +1137,16 @@ class DateTimeField(Field): if input_format.lower() == ISO_8601: try: parsed = parse_datetime(value) - except (ValueError, TypeError): - pass - else: if parsed is not None: return self.enforce_timezone(parsed) + except (ValueError, TypeError): + pass else: try: parsed = self.datetime_parser(value, input_format) + return self.enforce_timezone(parsed) except (ValueError, TypeError): pass - else: - return self.enforce_timezone(parsed) humanized_format = humanize_datetime.datetime_formats(input_formats) self.fail('invalid', format=humanized_format) diff --git a/rest_framework/filters.py b/rest_framework/filters.py index bdab97b58..63ebf05ef 100644 --- a/rest_framework/filters.py +++ b/rest_framework/filters.py @@ -140,12 +140,14 @@ class SearchFilter(BaseFilterBackend): ] base = queryset + conditions = [] for search_term in search_terms: queries = [ models.Q(**{orm_lookup: search_term}) for orm_lookup in orm_lookups ] - queryset = queryset.filter(reduce(operator.or_, queries)) + conditions.append(reduce(operator.or_, queries)) + queryset = queryset.filter(reduce(operator.and_, conditions)) if self.must_call_distinct(queryset, search_fields): # Filtering against a many-to-many field requires us to diff --git a/rest_framework/pagination.py b/rest_framework/pagination.py index 0255cfc7f..a4a5230ef 100644 --- a/rest_framework/pagination.py +++ b/rest_framework/pagination.py @@ -31,7 +31,7 @@ def _positive_int(integer_string, strict=False, cutoff=None): if ret < 0 or (ret == 0 and strict): raise ValueError() if cutoff: - ret = min(ret, cutoff) + return min(ret, cutoff) return ret @@ -95,7 +95,7 @@ def _get_displayed_page_numbers(current, final): # Now sort the page numbers and drop anything outside the limits. included = [ idx for idx in sorted(list(included)) - if idx > 0 and idx <= final + if 0 < idx <= final ] # Finally insert any `...` breaks diff --git a/rest_framework/renderers.py b/rest_framework/renderers.py index 406dda72c..779f0dd44 100644 --- a/rest_framework/renderers.py +++ b/rest_framework/renderers.py @@ -556,7 +556,10 @@ class BrowsableAPIRenderer(BaseRenderer): accepted = self.accepted_media_type context = self.renderer_context.copy() context['indent'] = 4 - content = renderer.render(serializer.data, accepted, context) + data = {k: v for (k, v) in serializer.data.items() + if not isinstance(serializer.fields[k], + serializers.HiddenField)} + content = renderer.render(data, accepted, context) else: content = None diff --git a/rest_framework/request.py b/rest_framework/request.py index ffbe4a367..6f4269fe5 100644 --- a/rest_framework/request.py +++ b/rest_framework/request.py @@ -250,6 +250,10 @@ class Request(object): else: self._full_data = self._data + # copy files refs to the underlying request so that closable + # objects are handled appropriately. + self._request._files = self._files + def _load_stream(self): """ Return the content body of the request, as a stream. diff --git a/rest_framework/status.py b/rest_framework/status.py index c016b63c6..d4522df3d 100644 --- a/rest_framework/status.py +++ b/rest_framework/status.py @@ -9,23 +9,23 @@ from __future__ import unicode_literals def is_informational(code): - return code >= 100 and code <= 199 + return 100 <= code <= 199 def is_success(code): - return code >= 200 and code <= 299 + return 200 <= code <= 299 def is_redirect(code): - return code >= 300 and code <= 399 + return 300 <= code <= 399 def is_client_error(code): - return code >= 400 and code <= 499 + return 400 <= code <= 499 def is_server_error(code): - return code >= 500 and code <= 599 + return 500 <= code <= 599 HTTP_100_CONTINUE = 100 diff --git a/rest_framework/templates/rest_framework/docs/document.html b/rest_framework/templates/rest_framework/docs/document.html index ef5f5966b..274eee4e3 100644 --- a/rest_framework/templates/rest_framework/docs/document.html +++ b/rest_framework/templates/rest_framework/docs/document.html @@ -13,7 +13,7 @@ {% if 'javascript' in langs %}{% include "rest_framework/docs/langs/javascript-intro.html" %}{% endif %} - +{% if document.data %} {% for section_key, section in document.data|items %} {% if section_key %}

{{ section_key }} @@ -28,3 +28,4 @@ {% for link_key, link in document.links|items %} {% include "rest_framework/docs/link.html" %} {% endfor %} +{% endif %} diff --git a/rest_framework/templates/rest_framework/docs/sidebar.html b/rest_framework/templates/rest_framework/docs/sidebar.html index c6ac26f66..7e20e2485 100644 --- a/rest_framework/templates/rest_framework/docs/sidebar.html +++ b/rest_framework/templates/rest_framework/docs/sidebar.html @@ -5,16 +5,18 @@